fix(interactive): arrow keys do not navigate the guided prompts on Windows - #476
Merged
Merged
Conversation
…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
marked this pull request as ready for review
August 10, 2026 13:38
aptracebloc
approved these changes
Aug 10, 2026
saadqbal
approved these changes
Aug 10, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #475.
The bug
On Windows, arrow keys stop navigating the guided prompts. In the
tb data ingesttask-typeSelect, pressing ↓ prints[Binto the filter line and the highlighted option never moves:Root cause
survey's Windows rune reader (terminal/runereader_windows.go) reads console records withReadConsoleInputWand recognises navigation from the virtual key codesVK_UP/VK_DOWN.Before reading it clears
ENABLE_ECHO_INPUT,ENABLE_LINE_INPUTandENABLE_PROCESSED_INPUT—but not
ENABLE_VIRTUAL_TERMINAL_INPUT.With that flag set, the console stops delivering arrows as
VK_*events and delivers them as ANSIescape sequences (
ESC[B) instead.surveysees three ordinary runes: theESCis droppedand
[Blands in the filter. NoVK_DOWNever 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.7is the newest release (go list -m -versionsshows nothing newer), so there isno upstream bump to take instead.
The fix
enableKeyEventInput()clearsENABLE_VIRTUAL_TERMINAL_INPUTon stdin for the duration of eachprompt and restores the caller's original mode after, so
survey's key-event path works regardlessof what turned the flag on. Wired into all three
surveyPromptermethods (Input,Select,Confirm) — the seam wheresurveyis actually called, so every prompt is covered whatever theentry point.
It is deliberately unable to break anything:
console_input_other.goreturns an empty func. POSIX uses raw mode,where
surveyparses the escape sequences itself and arrows already work.--no-input, no consoleattached, or a hardened environment that refuses the call:
GetConsoleModefails and promptingproceeds exactly as before.
would change terminal behaviour after the CLI exits.
nil, sodefer enableKeyEventInput()()cannot panic.golang.org/x/sysmoves from indirect to direct ingo.mod(already in the tree at v0.47.0);go.sumis 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.GOOS=windowsso the Windows-only file isactually linted:
gofmt -s,goimports -local,go vet,errcheck,ineffassign,misspell,staticcheck -checks all,-ST1005— all clean.Selectfails the test withinteractive.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 observedgreen on a Windows console. The reasoning is falsifiable and specific:
runereader_windows.goline 72 clears three input flags and not
ENABLE_VIRTUAL_TERMINAL_INPUT, and the reader'snavigation switch keys off
VK_UP/VK_DOWN— which the console does not emit while VT input ison. 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 returnsnil; safe to call repeatedly. Undergo teststdin is not a console, so on Windows this exercises theGetConsoleMode-failed branch.TestEverySurveyPromptClearsVirtualTerminalInput— AST walk requiring everysurvey.AskOnecallsite in
interactive.goto also callenableKeyEventInput. A fourth prompt added without thewrapper is invisible on macOS/Linux and in CI; this makes it a test failure.
TestConsoleInputBuildTagsCoverEveryPlatform— the two build-tagged halves stay mutuallyexclusive, exhaustive, and signature-identical, so no platform loses the symbol.
Workaround until this ships
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
surveyguided prompts (e.g. task-typeSelect) leaked[Binto the filter instead of moving the highlight when the console had virtual terminal input enabled.Adds
enableKeyEventInput(), which temporarily clearsENABLE_VIRTUAL_TERMINAL_INPUTon 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
surveyPrompterpath (Input,Select,Confirm) now runsdefer enableKeyEventInput()()beforesurvey.AskOne.golang.org/x/sysis promoted to a direct dependency for the Windows console APIs.New tests assert the restore func is never nil, that every
AskOnesite ininteractive.gois wrapped (AST check), and that the Windows/!windowsbuild halves stay aligned.Reviewed by Cursor Bugbot for commit e517370. Bugbot is set up for automated code reviews on this repo. Configure here.