Summary
The "RangeLink: Choose a destination to bind to" picker lists Terminal ("Jest (rangeLink-002)") alongside real shell terminals like Terminal ("claude"). Jest's terminal is a task-runner output surface; sending text to it is meaningless and may produce confusing UX (input is dropped, or worse, sent to the test harness). Binding to it should not be possible.
Reproduction
- Open the workspace and start the Jest watcher task — VS Code creates a
Terminal ("Jest (rangeLink-002)") entry.
- Run command
RangeLink: Choose a destination to bind to.
- Observe that the Jest terminal appears in the Terminals section of the picker and is selectable.
Root cause
The eligibility filter at packages/rangelink-vscode-extension/src/destinations/utils/isTerminalEligible.ts:30-31 only excludes terminals on two grounds:
terminal.exitStatus !== undefined — process already exited.
terminal.creationOptions.hideFromUser === true — internal IDE terminal (e.g., Cursor's background terminal).
A Jest task terminal satisfies neither — its process is live and it is not hidden. The picker is fed via getEligibleTerminals.ts (lines 16-39), which simply walks vscode.window.terminals through isTerminalEligible. There is no signal beyond those two checks. No name pattern, no creationOptions.pty check, no isTransient check.
Why detecting "accepts user input" is hard
VS Code's public Terminal API exposes no isReadOnly / acceptsInput / inputAllowed property. The structural signals that exist:
creationOptions is either TerminalOptions (shell-backed) or ExtensionTerminalOptions (carries a pty: Pseudoterminal). Jest's task terminal and our integration-test capturing terminals are both ExtensionTerminalOptions.
creationOptions.isTransient — set by some task-runner integrations but not contractually guaranteed.
terminal.shellIntegration — only present for shells that opted in via shell-integration scripts. Not all real shells set it, so it is a positive-but-not-complete signal.
terminal.state.isInteractedWith — tracks whether the user has typed into the terminal, not whether they can.
So there is no single property we can read. The most reliable structural rule is "filter terminals whose creationOptions is ExtensionTerminalOptions (i.e., the pty field is present)" — but that catches our own test capturing terminals, see next section.
Tension with our capturing test terminals
packages/rangelink-vscode-extension/src/__integration-tests__/helpers/capturingPtyHelpers.ts:30-63 creates capturing terminals via vscode.window.createTerminal({ name, pty }) — these are ExtensionTerminalOptions exactly like Jest's. They are not marked hideFromUser because the assisted-mode runner wants the human to see them. A naive "exclude all pty terminals" rule would block our own tests from binding.
Mitigations to evaluate during implementation:
- Mark capturing terminals
hideFromUser: true and rely on CMD_BIND_TO_TERMINAL_HERE (which binds the active terminal directly, bypassing eligibility). Picker-based bind tests would need a real shell terminal or a test-only escape hatch.
- Add a sentinel to the capturing terminal name and exempt it in
isTerminalEligible — fragile, leaks test concerns into prod code.
- Keep the filter purely structural (presence of
pty) and update the capturing-test helpers to install a shell-based fallback for picker-bind scenarios.
Resolved design decisions
- Prefer a structural rule over name-based heuristics. Detect whether the terminal supports interactive user input; do not hardcode test-runner names. The investigation above shows VS Code has no direct API for this — implementation must pick a structural proxy (likely
pty presence) and resolve the capturing-terminal tension.
- Present filtered terminals as disabled entries in the picker (not hidden), with a short tightly-controlled tooltip explaining why they are not bindable.
Files involved
packages/rangelink-vscode-extension/src/destinations/utils/isTerminalEligible.ts:13-31 — current eligibility predicate.
packages/rangelink-vscode-extension/src/destinations/utils/getEligibleTerminals.ts:16-39 — picker feeder.
packages/rangelink-vscode-extension/src/__integration-tests__/helpers/capturingPtyHelpers.ts:30-83 — test infrastructure that the fix must not break.
packages/rangelink-vscode-extension/src/ide/vscode/VscodeAdapter.ts:823-824 — adapter surface for terminals / activeTerminal.
Acceptance criteria
- Jest task-runner terminal does not appear as a selectable destination in the bind picker.
- If shown, the entry is rendered disabled with a brief tooltip explaining why it is not bindable.
- Integration tests using capturing pty terminals continue to pass.
- Eligibility decision is based on a structural signal, not a name allowlist/denylist.
- QA YAML gets a new TC covering "task-runner terminal is shown disabled in the bind picker".
Open implementation questions
- Which structural signal to settle on:
pty presence vs isTransient vs requiring shellIntegration. Each has false positives or negatives.
- Whether to add a typed flag to
creationOptions of our own capturing terminals so the eligibility predicate can recognize the exemption without name matching.
- Whether the disabled-entry tooltip needs to be localized via the existing message-catalog flow.
Related
Blocks #249 — must ship before the next release.
Summary
The "RangeLink: Choose a destination to bind to" picker lists
Terminal ("Jest (rangeLink-002)")alongside real shell terminals likeTerminal ("claude"). Jest's terminal is a task-runner output surface; sending text to it is meaningless and may produce confusing UX (input is dropped, or worse, sent to the test harness). Binding to it should not be possible.Reproduction
Terminal ("Jest (rangeLink-002)")entry.RangeLink: Choose a destination to bind to.Root cause
The eligibility filter at
packages/rangelink-vscode-extension/src/destinations/utils/isTerminalEligible.ts:30-31only excludes terminals on two grounds:terminal.exitStatus !== undefined— process already exited.terminal.creationOptions.hideFromUser === true— internal IDE terminal (e.g., Cursor's background terminal).A Jest task terminal satisfies neither — its process is live and it is not hidden. The picker is fed via
getEligibleTerminals.ts(lines 16-39), which simply walksvscode.window.terminalsthroughisTerminalEligible. There is no signal beyond those two checks. No name pattern, nocreationOptions.ptycheck, noisTransientcheck.Why detecting "accepts user input" is hard
VS Code's public Terminal API exposes no
isReadOnly/acceptsInput/inputAllowedproperty. The structural signals that exist:creationOptionsis eitherTerminalOptions(shell-backed) orExtensionTerminalOptions(carries apty: Pseudoterminal). Jest's task terminal and our integration-test capturing terminals are bothExtensionTerminalOptions.creationOptions.isTransient— set by some task-runner integrations but not contractually guaranteed.terminal.shellIntegration— only present for shells that opted in via shell-integration scripts. Not all real shells set it, so it is a positive-but-not-complete signal.terminal.state.isInteractedWith— tracks whether the user has typed into the terminal, not whether they can.So there is no single property we can read. The most reliable structural rule is "filter terminals whose
creationOptionsisExtensionTerminalOptions(i.e., theptyfield is present)" — but that catches our own test capturing terminals, see next section.Tension with our capturing test terminals
packages/rangelink-vscode-extension/src/__integration-tests__/helpers/capturingPtyHelpers.ts:30-63creates capturing terminals viavscode.window.createTerminal({ name, pty })— these areExtensionTerminalOptionsexactly like Jest's. They are not markedhideFromUserbecause the assisted-mode runner wants the human to see them. A naive "exclude all pty terminals" rule would block our own tests from binding.Mitigations to evaluate during implementation:
hideFromUser: trueand rely onCMD_BIND_TO_TERMINAL_HERE(which binds the active terminal directly, bypassing eligibility). Picker-based bind tests would need a real shell terminal or a test-only escape hatch.isTerminalEligible— fragile, leaks test concerns into prod code.pty) and update the capturing-test helpers to install a shell-based fallback for picker-bind scenarios.Resolved design decisions
ptypresence) and resolve the capturing-terminal tension.Files involved
packages/rangelink-vscode-extension/src/destinations/utils/isTerminalEligible.ts:13-31— current eligibility predicate.packages/rangelink-vscode-extension/src/destinations/utils/getEligibleTerminals.ts:16-39— picker feeder.packages/rangelink-vscode-extension/src/__integration-tests__/helpers/capturingPtyHelpers.ts:30-83— test infrastructure that the fix must not break.packages/rangelink-vscode-extension/src/ide/vscode/VscodeAdapter.ts:823-824— adapter surface forterminals/activeTerminal.Acceptance criteria
Open implementation questions
ptypresence vsisTransientvs requiringshellIntegration. Each has false positives or negatives.creationOptionsof our own capturing terminals so the eligibility predicate can recognize the exemption without name matching.Related
Blocks #249 — must ship before the next release.