Skip to content

fix(interactive): arrow keys do not navigate the guided prompts on Windows - #476

Merged
shujaatTracebloc merged 1 commit into
developfrom
fix/475-windows-arrow-keys
Aug 10, 2026
Merged

fix(interactive): arrow keys do not navigate the guided prompts on Windows#476
shujaatTracebloc merged 1 commit into
developfrom
fix/475-windows-arrow-keys

Conversation

@shujaatTracebloc

@shujaatTracebloc shujaatTracebloc commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Fixes #475.

The bug

On Windows, arrow keys stop navigating the guided prompts. In the tb data ingest task-type
Select, pressing ↓ prints [B into the filter line and the highlighted option never moves:

? Which task type?  [Use arrows to move, type to filter]
? [B[B

Root cause

survey's Windows rune reader (terminal/runereader_windows.go) reads console records with
ReadConsoleInputW and recognises navigation from the virtual key codes VK_UP / VK_DOWN.
Before reading it clears ENABLE_ECHO_INPUT, ENABLE_LINE_INPUT and ENABLE_PROCESSED_INPUT
but not ENABLE_VIRTUAL_TERMINAL_INPUT.

With that flag set, the console stops delivering arrows as VK_* events and delivers them as ANSI
escape sequences (ESC [ B) instead. survey sees three ordinary runes: the ESC is dropped
and [B lands in the filter. No VK_DOWN ever arrives, so navigation cannot fire.

This also explains the apparent regression with no change on our side: console input mode is
state on the console handle
, not something the CLI selects. A terminal that opts into VT input,
or any program in the session that sets the flag and does not restore it, flips the behaviour.

survey/v2 v2.3.7 is the newest release (go list -m -versions shows nothing newer), so there is
no upstream bump to take instead.

The fix

enableKeyEventInput() clears ENABLE_VIRTUAL_TERMINAL_INPUT on stdin for the duration of each
prompt and restores the caller's original mode after, so survey's key-event path works regardless
of what turned the flag on. Wired into all three surveyPrompter methods (Input, Select,
Confirm) — the seam where survey is actually called, so every prompt is covered whatever the
entry point.

It is deliberately unable to break anything:

  • No-op off Windowsconsole_input_other.go returns an empty func. POSIX uses raw mode,
    where survey parses the escape sequences itself and arrows already work.
  • No-op when stdin is not a console — piped/redirected input, --no-input, no console
    attached, or a hardened environment that refuses the call: GetConsoleMode fails and prompting
    proceeds exactly as before.
  • No-op when the flag is already clear — the common case; the console mode is left untouched.
  • Restores the mode the user's shell had, not a value we think is right — leaving VT input off
    would change terminal behaviour after the CLI exits.
  • Never returns nil, so defer enableKeyEventInput()() cannot panic.

golang.org/x/sys moves from indirect to direct in go.mod (already in the tree at v0.47.0);
go.sum is unchanged.

Verification

Run locally, output in the commit trail:

  • go test ./...16 packages ok, 0 failures, including 3 new tests.
  • GOOS=windows GOARCH=amd64 go build ./..., GOOS=windows GOARCH=arm64 go build ./...,
    GOOS=linux, GOOS=darwin — all build.
  • CI's standalone lint set, run both for the host and GOOS=windows so the Windows-only file is
    actually linted: gofmt -s, goimports -local, go vet, errcheck, ineffassign, misspell,
    staticcheck -checks all,-ST1005 — all clean.
  • The AST guard was proved to bite: removing the wrapper from Select fails the test with
    interactive.go: Select calls survey.AskOne without defer enableKeyEventInput()().

What I could not verify locally, stated plainly

I develop on macOS, so the fix itself is reasoned from survey's Windows source, not observed
green on a Windows console.
The reasoning is falsifiable and specific: runereader_windows.go
line 72 clears three input flags and not ENABLE_VIRTUAL_TERMINAL_INPUT, and the reader's
navigation switch keys off VK_UP/VK_DOWN — which the console does not emit while VT input is
on. That matches the reported symptom exactly (filter receives [B, selection never moves).

It still needs one confirmation on the affected Windows machine before this is called done. The
tests here pin the wiring and the safety contract; they cannot pin the console behaviour.

Tests added

  • TestEnableKeyEventInputIsAlwaysSafe — never returns nil; safe to call repeatedly. Under
    go test stdin is not a console, so on Windows this exercises the GetConsoleMode-failed branch.
  • TestEverySurveyPromptClearsVirtualTerminalInput — AST walk requiring every survey.AskOne call
    site in interactive.go to also call enableKeyEventInput. A fourth prompt added without the
    wrapper is invisible on macOS/Linux and in CI; this makes it a test failure.
  • TestConsoleInputBuildTagsCoverEveryPlatform — the two build-tagged halves stay mutually
    exclusive, exhaustive, and signature-identical, so no platform loses the symbol.

Workaround until this ships

tb data ingest "<path>" --name <name> --task <task> --intent train --no-input

Or type to filter (e.g. masked) instead of using arrows.

🤖 Generated with Claude Code


Note

Low Risk
Scoped to interactive CLI prompting on Windows with safe fallbacks; no auth, data, or cluster behavior changes.

Overview
Fixes #475: on Windows, arrow keys in survey guided prompts (e.g. task-type Select) leaked [B into the filter instead of moving the highlight when the console had virtual terminal input enabled.

Adds enableKeyEventInput(), which temporarily clears ENABLE_VIRTUAL_TERMINAL_INPUT on stdin for the duration of each prompt and restores the previous console mode afterward. On non-Windows builds it is a no-op; on Windows it no-ops when stdin is not a console or the flag is already off.

Every surveyPrompter path (Input, Select, Confirm) now runs defer enableKeyEventInput()() before survey.AskOne. golang.org/x/sys is promoted to a direct dependency for the Windows console APIs.

New tests assert the restore func is never nil, that every AskOne site in interactive.go is wrapped (AST check), and that the Windows/!windows build halves stay aligned.

Reviewed by Cursor Bugbot for commit e517370. Bugbot is set up for automated code reviews on this repo. Configure here.

…ndows

Pressing the down arrow in the `tb data ingest` task-type Select printed "[B"
into the filter and never moved the selection, so the list could not be
navigated at all.

survey's Windows rune reader (terminal/runereader_windows.go) reads console
records with ReadConsoleInputW and recognises navigation from the VIRTUAL KEY
codes VK_UP / VK_DOWN. Before reading it clears ENABLE_ECHO_INPUT,
ENABLE_LINE_INPUT and ENABLE_PROCESSED_INPUT -- but not
ENABLE_VIRTUAL_TERMINAL_INPUT. With that flag set the console stops delivering
arrows as VK_* events and delivers them as ANSI escape sequences (ESC '[' 'B'),
so survey sees three ordinary runes: the ESC is dropped and "[B" lands in the
filter. No VK_DOWN ever arrives, so navigation cannot fire.

That also explains the apparent regression with no change on our side: console
input mode is state on the console handle, not something the CLI selects. A
terminal that opts into VT input, or any program in the session that sets the
flag and does not restore it, flips the behaviour. survey/v2 v2.3.7 is the
newest release, so there is no upstream bump available.

Clear ENABLE_VIRTUAL_TERMINAL_INPUT on stdin for the duration of each prompt
and restore the caller's original mode after, so survey's key-event path works
regardless of what enabled the flag. The helper is a no-op off Windows (POSIX
uses raw mode, where survey parses the escape sequences itself) and a no-op
when the mode cannot be read or set -- stdin piped, no console attached -- so
interactive niceness can never break a run.

Tests: the helper never returns nil and is safe to call repeatedly; the two
build-tagged halves keep identical signatures; and an AST guard requires every
survey.AskOne call site in interactive.go to be wrapped, since a missing
wrapper is invisible on macOS/Linux and in CI. Verified the guard fails when
the wrapper is removed from Select.

Refs #475

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@shujaatTracebloc shujaatTracebloc self-assigned this Aug 10, 2026
@shujaatTracebloc
shujaatTracebloc marked this pull request as ready for review August 10, 2026 13:38
@shujaatTracebloc
shujaatTracebloc merged commit 829dd00 into develop Aug 10, 2026
35 checks passed
@shujaatTracebloc
shujaatTracebloc deleted the fix/475-windows-arrow-keys branch August 10, 2026 13:54
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.

data ingest: arrow keys do not navigate the guided prompts on Windows (ENABLE_VIRTUAL_TERMINAL_INPUT breaks survey's VK_UP/VK_DOWN path)

4 participants