Skip to content

Jest task-runner terminal appears in the R-D bind picker #592

@couimet

Description

@couimet

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

  1. Open the workspace and start the Jest watcher task — VS Code creates a Terminal ("Jest (rangeLink-002)") entry.
  2. Run command RangeLink: Choose a destination to bind to.
  3. 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:

  1. terminal.exitStatus !== undefined — process already exited.
  2. 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.

Metadata

Metadata

Assignees

Labels

priority:highHigh priorityscope:vscode-extrangelink-vscode-extension packagetype:bugBug or defect in existing functionality

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions