Cursor jumps and flashes when deleting text in input field

Open 💬 7 comments Opened Apr 3, 2026 by Dioman-Keita
💡 Likely answer: A maintainer (github-actions[bot], contributor) responded on this thread — see the highlighted reply below.

What version of Codex CLI is running?

codex-cli 0.118.0

What subscription do you have?

free

Which model were you using?

gpt-5.4 medium

What platform is your computer?

Microsoft Windows NT 10.0.26200.0 x64

What terminal emulator and version are you using (if applicable)?

CMD, Antigravity terminal

What issue are you seeing?

When deleting characters in the input field, the cursor frequently jumps or changes position very rapidly. This creates a flashing effect in the UI. The issue also occurs occasionally while typing, but it is most noticeable during deletions.

What steps can reproduce the bug?

Uploaded thread: 019d3b53-4440-7a03-9fd0-013b0e9f485d
It is very difficult to capture a screenshot of this behavior because the flashing happens so quickly when deleting.

What is the expected behavior?

The cursor should consistently remain at the logical insertion point during both typing and deletion, without flashing or unexpected movement.

Additional information

_No response_

View original on GitHub ↗

7 Comments

github-actions[bot] contributor · 4 months ago

Potential duplicates detected. Please review them and close your issue if it is a duplicate.

  • #15146

Powered by Codex Action

etraut-openai contributor · 4 months ago

Are you able to repro the problem outside of the Antigravity integrated terminal? If not, this is likely a problem in Antigravity, and you should report the problem to its maintainers.

Dioman-Keita · 4 months ago

I was able to reproduce the problem in VSCode’s integrated terminal (Version 2.6.22, x64). However, it works correctly in CMD (Microsoft Windows [Version 10.0.26200.8037], Windows NT 10.0.26200.0 x64). I initially thought it didn’t, but after testing again, the issue does not occur there.
For reference, I am using Codex CLI version 0.118.0.

snowyukitty · 2 months ago

@etraut-openai , thanks for tracking this.

I’ve reproduced this and prepared a branch that fixes it:
https://github.com/snowyukitty/codex/tree/fix-tui-cursor-sync-frame

I can’t open an upstream PR from this account because GitHub rejected CreatePullRequest on openai/codex.
If you want, I can keep the patch details here or open the PR once invited.

Validation:

  • just fmt
  • just test -p codex-tui
  • just fix -p codex-tui
  • Windows ConPTY trace now shows the cursor bytes before ?2026l, with nothing after the sync-end marker.

If this direction looks useful, I’m happy to share the patch details here or open the PR once invited.
Thanks for maintaining Codex. I hope this is helpful.

noaho04 · 1 month ago

Setting the cursor position before showing the cursor and hiding the cursor before the scrollback insertion fixed the issue for me on Windows (using alacritty).

snowyukitty · 4 days ago

@noaho04's two-part diagnosis holds up, and only one of the two parts has landed.

#40166 fixed the second half: try_draw_with_size now positions the caret before
showing it, so a single draw no longer exposes it at its previous position. The first
half — hiding the caret before the scrollback insertion — is still missing, and it is
the half that covers everything a frame writes before that draw.

Why the remaining half matters. A frame does not reach the terminal as one write.
The backend writes through std::io::stdout(), which is a LineWriter with a 1 KiB
buffer that also flushes on every newline, and history insertion emits Print("\r\n")
per line. So a frame that pushes N history rows crosses the write boundary at least N
times, plus once per 1 KiB of diff. A terminal that implements synchronized updates
(DEC private mode 2026) holds those chunks until the frame ends. A terminal that does
not paints each chunk as it arrives, and every chunk ends with the caret wherever the
last MoveTo left it — the footer, then the status line, then the composer.

That mechanism also explains why this and #17823 look like different bugs: xterm.js only
implemented mode 2026 in 6.0.0 (xtermjs/xterm.js#5453), so Tabby and Wave discard the
markers outright. Worth saying plainly, though: hiding the caret does not depend on
knowing which terminals honour mode 2026, or on how completely they honour it. A caret
that is not visible cannot be painted at the wrong position by anything.

The change. Hide the caret at the top of both synchronized frames in tui.rs.
Nothing has to remember to bring it back, and the reason is worth stating precisely,
because I got it slightly wrong the first time. It is not that every frame ends in a
draw — a frame can fail partway. It is that try_draw_with_size writes a caret
decision in both of its arms, and that Terminal::hide_cursor and
Terminal::show_cursor write unconditionally rather than short-circuiting on
hidden_cursor. So a frame that reaches its draw ends with the caret in the state that
frame asked for, whatever preceded it. A frame that does not reach its draw failed
writing to stdout; the caret stays hidden until the next frame that succeeds, or until
restore_common and Drop for Terminal restore it at teardown.

One commit on current main, one line per frame plus two tests. Tui is hard-wired to
CrosstermBackend<Stdout>, so those two lines cannot themselves be pointed at a capture
backend without generifying it. The tests instead pin the two properties that make them
correct: a caret hidden before a frame's raw writes is taken back over by the draw and
becomes visible exactly once, after the final position; and both visibility writes reach
the terminal even when the flag already agrees, since the restore path writes Show
straight to stdout behind Terminal's back.

Validation on x86_64-pc-windows-msvc, rustc 1.95.0:

  • cargo test -p codex-tui --lib — 3738 passed, 3 failed. The three are environmental,

expecting ~\AppData\... where this machine reports C:\Users\...; unmodified main
reads 3736 passed, 3 failed with the same three.

  • cargo clippy -p codex-tui --all-targets --all-features -- -D warnings — clean.

I cannot open a PR — openai/codex only accepts them from collaborators — so the
branch is here for anyone who can carry it.

snowyukitty · 4 days ago

Follow-up to my last comment here, which offered the caret hide with the caveat that it had no direct test.

It has one now. vt100 implements no synchronized-output mode, so a frame drawn against it is presented piece by piece — the same reason the caret is visible mid-frame in the terminals these two issues report. Driving a real Tui::draw into a vt100 screen behind a LineWriter and sampling the caret at each write boundary shows, on today's main, one frame painting a visible caret in four different places; with the caret hidden for the frame, one.

Branch, two commits on 2df6705423: https://github.com/snowyukitty/codex/tree/fix/tui-caret-frame-regression-test

The first commit is the fix alone and stands on its own; the second is the test plus a defaulted backend parameter on Tui that changes no call sites. The details and the caveats are on #17823 rather than repeated here.

@noaho04 — you reported that positioning before showing and hiding before the scrollback insertion together fixed this on alacritty. #40166 landed the first; this is the second, now with a trace showing what it removes.