Skip to content

fix(shellUtils): build terminal commands for the shell that runs them (#1822) - #2092

Merged
abdelrahmanomar1515 merged 6 commits into
mainfrom
fix-1822-shell-detection
Aug 7, 2026
Merged

fix(shellUtils): build terminal commands for the shell that runs them (#1822)#2092
abdelrahmanomar1515 merged 6 commits into
mainfrom
fix-1822-shell-detection

Conversation

@abdelrahmanomar1515

@abdelrahmanomar1515 abdelrahmanomar1515 commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Fixes #1822.

Why

On Windows with cmd.exe as the terminal shell, "Create a new Databricks project" was
broken. The wizard built one POSIX command line and sent it to whatever shell the
terminal was running, so cmd rejected it token by token and the CLI never started:

C:\...\Temp>clear; echo "Executing: databricks bundle init --output-dir "c:\...\databricks-test"
'clear' is not recognized as an internal or external command,
'Follow' is not recognized as an internal or external command,
'Press' is not recognized as an internal or external command,

clear, ; as a separator, and read are all POSIX-only. cmd needs cls, & and
pause.

The reported wizard turned out not to be the only casualty. The same shared helpers are
used by "Databricks SSH Tunnel" and az login, and both were broken in cmd on
main for the same reason — details in their own sections below.

The root cause is structural, not a missing branch. shellUtils read env.shell inside
every helper and split only "PowerShell" vs "everything else", using a substring match on
the full path — so cmd.exe fell into the POSIX bucket. Because the branching sat behind a
module-level VS Code read, none of it could be reached from a unit test, so this class of
bug had no way to be caught. Fixing only the clear/read symptoms would have left the
next shell-specific bug just as invisible, which is why this is a refactor rather than a
two-line patch.

What changed

One classifier instead of scattered predicates.
detectShellKind(shell, platform) -> "cmd" | "powershell" | "posix" is a pure function;
every helper takes that kind. env.shell is now read in exactly one place,
currentShellKind(), matching the existing venvInterpreterPath injection pattern in
this repo. Detection matches on the shell's basename, so
C:\cmder\...\bash.exe is no longer mistaken for cmd.

Terminals still resolve their own shell. I deliberately did not pin shellPath.
Passing an explicit executable makes VS Code build a synthetic profile from
{path, args} and never supply the resolved profile's defaults
(terminalProfileResolverService.ts:113-124) —
and on macOS the fallback profile is what appends --login. So pinning
shellPath: env.shell would silently turn a login shell into a non-login one, stop
~/.zprofile being sourced, and change PATH. env.shell exposes only profile.path,
never args, so those args can't be recovered.

Per file:

File Change
utils/shellUtils.ts ShellKind + detectShellKind; all helpers parameterised; per-shell quoting; clearCmd/commandSeparator/echoLine/hasCmdUnsafeChars added
utils/shellUtils.test.ts New — 60 cases across three layers
bundle/BundleInitWizard.ts Assembles the line via the helpers; refuses a cmd-unsafe output dir
cli/CliWrapper.ts escapedCliPathFor(kind); escapedCliPath delegates to it — which also fixes the SSH tunnel, see below
configuration/auth/AzureCliCheck.ts Same defect in the az login line; also quotes the az path and the tenant
run/RunCommands.ts Explicit unknown-shell quoting (behaviour unchanged — see below)

Bugs found once the logic became testable

Each is a real defect on main, not hypothetical:

  • pwsh[.exe] was unhandled — PowerShell 6+/7, the default on modern Windows. It
    matched neither "powershell" nor cmd, so those users got POSIX read, which does not
    exist in PowerShell.
  • PowerShell paths were double-quoted, which interpolates.
    C:\Users\me\$RECYCLE.BIN expanded to C:\Users\me\.BIN, and a directory named
    C:\a$(whoami)b would execute whoami. Both PowerShell and POSIX now use single
    quotes, which are literal.
  • escapedCliPath quoted the CLI wrongly for both Windows shells, which broke the
    SSH tunnel as well as the init wizard — see the section below.
  • escapedCliPath used .replace, not .replaceAll — every quote after the first
    was left unescaped.
  • az login interpolated both the az path and the tenant bare. A custom azBinPath
    containing spaces (C:\Program Files\Azure CLI\az.cmd) split into several arguments,
    and the tenant — which comes from a token's iss claim, i.e. external data reaching a
    command line — was unquoted. Both are quoted now. (Quoting a bare command name still
    resolves via PATH in all three shells, so the default az is unaffected.)
  • POSIX used echo, whose builtin form interprets backslash escapes in zsh and dash
    even inside single quotes: \t printed a tab, and \c truncated the rest of the
    line. Now printf '%s\n', which is verbatim everywhere. (The message is a printf
    argument, not the format string, so a literal % is safe.)
  • The read pause needs a named variable; every shorter spelling breaks a shell.
    Bare read is a usage error in dash (read: arg count, exit 2), and in fish it
    exits 0 but echoes the keypress back into the terminal. read _ fixes dash but is an
    error in fish, where _ is read-only. read -r name is an error in fish too — it has no
    -r flag. Now read discard, the only form sh, bash, zsh, dash and fish all
    accept. A failed pause isn't cosmetic: the following exit closes the terminal and
    discards exactly the CLI error the pause exists to keep on screen. (Matrix in the testing
    section below.)
  • cmd's echo printed shell operators as operators. The message was interpolated
    unquoted, so a banner containing &, | or > was parsed rather than printed — and the
    banner embeds the user's output directory, so a folder named a&b made cmd try to run
    b as a command ('pipe' is not recognized...). Now caret-escaped. Quoting isn't an
    option: cmd's echo prints the rest of the line raw, so the quotes would show. The
    escaping skips text already inside double quotes, where cmd treats operators as literal
    and a caret would itself print — that's the shape the real banner has.
  • The cmd separator had a leading space, so echo one & ... printed one with a
    trailing space, for the same raw-text reason. Now & rather than &.
  • %VAR% cannot be escaped in cmd — it expands even inside double quotes. Output dir
    C:\p%TEMP%q\proj would have scaffolded into a directory the user never chose, after
    which getSubProjects reports "no Databricks projects detected". BundleInitWizard now
    refuses up front with an actionable message. (!VAR! is included too, since it expands
    the same way under delayed expansion.)

The SSH tunnel had the same bug, and is fixed by the same change

Not just the init wizard: "Databricks SSH Tunnel" was broken in cmd on main too, and
in pwsh. SshCommands.launchSshTunnel sends ${this.cli.escapedCliPath} …
(src/ssh/SshCommands.ts:435), and main's getter had no cmd branch at all:

get escapedCliPath(): string {
    return isPowershell()
        ? `& "${this.cliPath.replace('"', '\\"')}"`
        : `'${this.cliPath.replaceAll("'", "\\'")}'`;   // ← cmd landed here
}

The consequence differs per shell, and neither starts the tunnel:

  • cmd got the single-quoted POSIX form. cmd has no single-quote semantics, so the
    quotes are part of the name and it looks for a program literally called
    'C:\…\databricks.exe'is not recognized as an internal or external command.
  • pwsh failed isPowershell()'s "powershell" substring match, so it also got
    single quotes — and without the & call operator a quoted string on its own line is
    just a string expression, so PowerShell prints the path rather than running it.

No change to SshCommands.ts was needed: escapedCliPath now delegates to
escapedCliPathFor(currentShellKind()), which routes cmd to double quotes and PowerShell
to & '…'. The call site was already correct — the terminal is created without
shellPath (SshCommands.ts:428-433), which is exactly the precondition the getter
documents, so the default profile really is the shell that parses the line. That's what
distinguishes it from RunCommands below.

The tunnel's arguments need no quoting: getSshConnectCommand
(CliWrapper.ts:128-142) emits only internally-generated tokens — ssh connect,
--ide=vscode|cursor, --auto-approve, --auto-start-cluster, a cluster ID, and an
accelerator from a hardcoded list — with no spaces or metacharacters. And it's a single
command, so none of the clear/read/separator/echo syntax that varies per shell is
involved. Worth noting for a follow-up: those args are interpolated unquoted, which is
fine for today's fixed set but would silently become a bug if a flag ever carried a
user-supplied value. There's also no src/ssh/ test file, so this path is covered only
indirectly through escapeExecutableForTerminal's round-trips.

RunCommands is deliberately unchanged in behaviour

runFileUsingDbconnect sends to window.activeTerminal ?? createTerminal(). For a
reused terminal the shell is not env.shell, and TerminalState.shell doesn't exist in
the @types/vscode@1.86.0 this extension targets, so no escaping choice is safe.
main's "…" quoting happens to parse in both cmd and POSIX, so it survives a
mismatch by accident; switching its POSIX branch to single quotes would have newly
broken a focused cmd tab (cmd would look for a program literally named
'C:\Python\python.exe'). It now calls explicit escapePathArgumentForUnknownShell /
escapeExecutableForUnknownShell helpers that keep the double quotes and say why. The
real fix there is a ProcessExecution/task with an argv array, which needs no quoting —
left as a follow-up.

Known gap: fish and backslashes

escapePathArgument's POSIX branch is not correct for fish, which — unlike sh, bash,
zsh and dash — treats \ as an escape inside single quotes. Two shapes go wrong:

output dir sh/bash/zsh/dash fish
/Users/me/two\\slashes verbatim arrives as two\slashes
/Users/me/trailing\ verbatim Unexpected end of string, quotes are not balanced

This is reachable, not theoretical: a directory literally named two\\slashes is legal on
macOS and Linux, and I confirmed it can be created. The first shape scaffolds into a
directory the user didn't choose (the same failure mode as %VAR% in cmd); the second
fails to parse.

I've left this out of this PR deliberately — it needs a fourth ShellKind (fish is
currently classified posix, which is right for clear/;/printf and wrong only for
backslash quoting), and that's a wider change than the bug this PR is about. Everything
else the wizard emits was verified byte-identical in fish. Happy to file it as a separate
issue.

How this was tested

Manually on Windows, across several terminal.integrated.defaultProfile.windows
settings — this is the bug report's scenario and it now works.

yarn test:unit669 passing, 0 failing with PowerShell 7.6.2 and fish 4.8.1 present
(599 before this branch, so 70 are new). Verified green in all three shapes this affects:
with pwsh and $TERM set, with pwsh and $TERM unset (the CI shape, via
env -u TERM), and with no pwsh at all. yarn test:lint clean.

fish is included in the POSIX round-trips, probed for on PATH so the tests skip
rather than fail where it isn't installed — neither CI image has it, nor does macOS out of
the box. It earns its place by not being strictly POSIX, which is how the read _ bug
above was found. The full read matrix, measured by running each candidate in each shell:

candidate sh bash zsh dash fish
read ok ok ok X arg count X echoes the input back
read _ ok ok ok ok X read-only variable
read -r name ok ok ok ok X no -r flag
read discard ok ok ok ok ok

Three layers, chosen so the weaknesses of each are covered by another:

  1. Table-driven classification. detectShellKind across 13 shell/platform pairs plus
    regression cases: the cmder false positive, cmd on Linux, pwsh on macOS, and the
    empty-shell fallback per platform. Both inputs are parameters, so these are
    deterministic on any host OS — no process.platform patching and no mocking anywhere
    in the suite.
  2. Exact command strings. The full bundle-init line is asserted verbatim for cmd,
    PowerShell and POSIX, so a reviewer can read the expected output for each shell
    directly in the test.
  3. Round-trip execution against real shells. String equality only proves the code
    built what the author intended — not that the shell agrees. Reverting the PowerShell
    single-quoting fix makes the round-trip fail with C:\a$VAR\dollar arriving as
    C:\a\dollar, i.e. real PowerShell silently dropping the variable. The generated commands are
    executed through /bin/sh, zsh, bash, dash and fish, and through cmd and
    PowerShell, asserting output matches input byte for byte across awkward messages and
    paths: embedded quotes of both kinds, $VAR, `cmd`, $(cmd), &/|/>,
    backslashes. This layer is what caught the echo and read bugs; the string assertions
    alone had passed.

Verifying the tests actually bite. Since a test that passes either way proves nothing,
I reverted each fix individually and recorded the failures:

Fix reverted Tests failing
PowerShell single-quoting 6
printf instead of echo 6 — in sh/zsh/dash only; bash passed, matching the real defect
read discardread _ 6 (incl. both fish round-trips)
read discard → bare read 7 — dash and fish; the fish echo-back is caught only by the new silent-consume assertion
%/! guard 1
empty-shell fallback 1

Every fix has at least one test that fails without it. Doing this also exposed two
weaknesses in my own tests, both times an assertion too weak to see a real failure:

  • the read round-trip originally used || true, which masked dash's exit-2 usage error.
    It now asserts empty stderr and status !== 2.
  • that stderr-and-status check still wasn't enough: bare read in fish exits 0 with
    empty stderr while echoing the keypress back into the terminal, so it slipped through.
    A second round-trip now asserts the pause consumes its line silently (stdout === ""),
    which is the only assertion that catches it.

Reviewer notes

  • The round-trip layer earned its keep, and the CI matrix is what proved it. Neither
    cmd nor PowerShell can be exercised on a macOS dev machine, so both CI legs found real
    problems that a green local run had missed:
    • The Linux leg has pwsh installed (PowerShell is probed for rather than gated on
      platform), so it ran those four tests and caught two Mocha timeouts from per-case
      pwsh spawns plus two assertions that depended on $TERM.
    • The Windows leg then caught the two genuine echo/separator defects listed above.
      Both are fixed and both legs are now green: Windows 660 passing / 0 failing with all
      ten cmd and PowerShell round-trips executing (non-trivial durations, 0 pending), Linux 663
      passing / 0 failing. So every assertion for the two shells [BUG] 'clear' is not recognized as an internal or external command #1822 is about has now run
      against a real shell, not just against a string comparison.
  • The cmd round-trips run the generated line from a temp .cmd file rather than
    cmd /c <line>. Passing it as an argv entry lets Node apply its own Windows quoting,
    which escapes an embedded " as \" — a sequence cmd doesn't understand — so the test
    would measure Node's escaping instead of ours.
  • Five commits, reviewable in order: (1) the bundle-init fix plus the shellUtils
    refactor — which is also where the SSH tunnel fix lands, via escapedCliPath, (2)
    az login, which had the identical defect, (3) making the PowerShell round-trips pass on
    a real pwsh, (4) the two cmd echo/separator fixes the Windows leg found, (5) the
    read discard fix for fish.
  • Three user-facing commands were broken in cmd on main, not one: the init wizard
    (reported), the SSH tunnel, and az login. All three are fixed here. The manual Windows
    testing covered the wizard — the bug report's scenario — so the tunnel and az login
    paths are verified by the round-trip tests rather than by hand, and are worth a manual
    look if you have a Windows box with cmd as the default profile.
  • echoLine takes a single line and callers emit blank lines as separate commands.
    The old code embedded \n in a double-quoted echo, which isn't portable.
  • Once engines.vscode moves past 1.86, terminal.state.shell becomes available and
    would make a good dev-only assertion: compare it against detectShellKind(env.shell)
    and log a warning on disagreement. That would also cover sub-shells and WSL distro
    wrappers, which basename matching can't see.
  • Follow-ups I'd file rather than grow this PR: fish's backslash quoting (section
    above — needs a fourth ShellKind), RunCommands moving to a ProcessExecution with an
    argv array, and the SSH tunnel's unquoted args.

This pull request and its description were written by Isaac.

@abdelrahmanomar1515
abdelrahmanomar1515 marked this pull request as ready for review August 7, 2026 08:44

@rugpanov rugpanov left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed via multi-source pass (Codex + Claude deep review + independent verification). Solid, well-reasoned refactor — commands are now built in the correct shell dialect. No blocking issues; the cmd caret-escaping, POSIX/PowerShell quoting, and call-site shell kinds all check out. One narrow nit: on a pwsh-default host, the reused-terminal (RunCommands) path can emit a stray & prefix into a non-pwsh terminal — not a hard bug, and the documented argv-array follow-up is the real fix.

@abdelrahmanomar1515
abdelrahmanomar1515 enabled auto-merge (squash) August 7, 2026 15:31
…#1822)

"Create a new Databricks project" was broken on Windows with cmd.exe as the
terminal shell. The wizard built one POSIX command line and sent it to
whatever shell the terminal was running, so cmd rejected it token by token
and the CLI never started:

    C:\...\Temp>clear; echo "Executing: databricks bundle init ..."
    'clear' is not recognized as an internal or external command,
    'Follow' is not recognized as an internal or external command,
    'Press' is not recognized as an internal or external command,

clear, ";" as a separator and read are all POSIX-only; cmd needs cls, " & "
and pause.

The cause is structural rather than a missing branch. shellUtils read
env.shell inside every helper and split only "PowerShell" vs everything
else, using a substring match on the full path, so cmd.exe fell into the
POSIX bucket. Because the branching sat behind a module-level vscode read,
none of it was reachable from a unit test, which is why this shipped.

Introduce detectShellKind(shell, platform) -> "cmd" | "powershell" |
"posix" as a pure function and parameterise every helper on the result.
env.shell is now read in exactly one place, currentShellKind(), following
the platform-injection pattern already used by venvInterpreterPath.
Matching is on the basename, so C:\cmder\...\bash.exe is no longer
mistaken for cmd.

The terminals keep resolving their own shell rather than pinning shellPath:
supplying an explicit executable makes VS Code drop the resolved profile's
args, which is what makes macOS shells login shells.

Bugs found once the logic became testable, each live on main:

- pwsh[.exe] was unhandled. PowerShell 6+/7 is the default on modern
  Windows; it matched neither "powershell" nor cmd, so those users got
  POSIX read, which does not exist there.
- PowerShell paths were double-quoted, which interpolates. A directory
  C:\a$(whoami)b would execute whoami, and $RECYCLE.BIN expanded to .BIN.
  Both PowerShell and POSIX now single-quote, which is literal.
- escapedCliPath used .replace, not .replaceAll, so every quote after the
  first was left unescaped.
- POSIX used echo, whose builtin form interprets backslash escapes in zsh
  and dash even inside single quotes: \t printed a tab and \c truncated the
  line. Now printf '%s\n', with the message as an argument rather than the
  format string, so a literal % is safe.
- Bare read is a usage error in dash ("read: arg count", exit 2). The
  hold-open step failed and the following exit closed the terminal,
  discarding the CLI error it exists to keep readable. Now read _.
- %VAR% cannot be escaped in cmd, since it expands inside double quotes
  too. BundleInitWizard now refuses such an output directory instead of
  scaffolding into a directory the user never chose.

RunCommands sends to a reused window.activeTerminal whose shell cannot be
detected on the API level this extension targets, so it keeps main's
double quotes via explicit *ForUnknownShell helpers; switching it to
single quotes would newly break a focused cmd tab.

Tests: 53 cases over three layers, chosen so each covers the others' blind
spots. Table-driven classification across shell/platform pairs, exact
expected command lines per shell, and round-trip execution through real
shells (sh, bash, zsh, dash, plus cmd and PowerShell where present)
asserting output matches input byte for byte. The round-trip layer is what
caught the echo and read defects; the string assertions alone had passed.
Every fix above was confirmed to have at least one test that fails when it
is reverted.

Fixes #1822

Co-authored-by: Isaac
AzureCliCheck had the same defect as the bundle init wizard: it hardcoded
";" as a separator and POSIX `echo`, then sent the line to whatever shell
the terminal was running. On Windows with cmd.exe that fails the same way
as #1822 -- cmd has no ";" separator, so the login command and the
hold-open step are parsed as garbage and `az login` never runs.

Assemble it from the shell-aware helpers instead, resolving the kind once
via currentShellKind(). The terminal is created without `shellPath`, so it
runs the resolved default profile, which is what currentShellKind reports.

Two related fixes in the same line:

- The az executable is now quoted. It was interpolated bare, so a custom
  azBinPath containing spaces (`C:\Program Files\Azure CLI\az.cmd`) split
  into multiple arguments. Quoting a bare command name still resolves via
  PATH in all three shells, so the default "az" is unaffected.
- The tenant is now quoted. It comes from a token's `iss` claim, i.e.
  external data reaching a command line, and was interpolated bare.

Also drops the empty-string placeholders the old template left behind:
`--use-device-code` and `-t` are appended to an argument array rather than
always emitted, so the command no longer contains runs of double spaces
when either is absent.

Tests: 7 cases asserting the assembled line verbatim for cmd, PowerShell
and POSIX, plus the tenant-absent, codespaces, spaces-in-path and
tenant-quoting cases. Confirmed to bite: reverting the separator to a
hardcoded ";" fails the cmd case, and reverting cmd's `pause` to `read`
fails it too.

Co-authored-by: Isaac
The Linux CI runner has pwsh installed, so the probe found it and these four
tests ran for real rather than skipping -- which is what they were written
for, and they failed. Three defects, all in the tests rather than in the
code under test:

- Two timed out. Each looped over 6-7 inputs spawning a fresh pwsh, and a
  PowerShell cold start is ~0.5-1s, so six sequential spawns blew Mocha's
  2000ms default. The POSIX round-trips never hit this because sh and dash
  start in single-digit milliseconds. Batch every case into one invocation
  joined by our own commandSeparator, which also exercises the separator,
  and raise the timeout on both windows-shell suites.

- `Clear-Host` on Linux shells out to `clear`, which needs $TERM. There is
  no TTY under CI, so it warns on stderr and the assertion for empty stderr
  failed on the environment rather than on the command. Filter that one
  line.

- `Clear-Host` writes its clear sequences to stdout, so comparing against
  "ok" failed on the escape codes. Assert on them instead: their presence
  is the proof it cleared, and "ok" surviving after them is the proof the
  separator did not swallow the next command.

Verified against PowerShell 7.6.2 (via nix, not installed system-wide):
663 passing, 0 failing, 5 pending -- the same pending count CI reported, so
all four now execute here. Reverting the PowerShell single-quoting fix makes
the round-trip fail with `C:\a$VAR\dollar` arriving as `C:\a\dollar`, so the
test constrains the shipped behaviour rather than just passing.

Also ran 8 consecutive suites (4 with pwsh, 4 without) to confirm a lone
failure seen in one 12s run was harness contention, not flakiness here.

Co-authored-by: Isaac
…ce before &

The Windows CI leg ran the cmd round-trips for the first time and found two
real defects in the generated command lines, plus one bad assertion.

`echoLine` interpolated the message into cmd's `echo` unquoted, so any shell
operator in it was parsed rather than printed. The bundle-init banner embeds
the user's output directory, so a folder named `a&b` made cmd try to run `b`
as a command:

    'pipe' is not recognized as an internal or external command,

Escape `^ & | < > ( )` with a caret instead. Quoting is not an option here:
cmd's `echo` prints the rest of the line raw, so the quotes would show. The
escaping deliberately skips text already inside double quotes -- cmd treats
operators there as literal, and a caret would *print*, which would corrupt
the quoted path the banner embeds. One regex pass with an alternation keeps
quoted runs intact.

The cmd separator loses its leading space, for the same reason: `echo one &`
printed "one" with a trailing space, since everything up to the `&` is text.

The third failure was `Clear-Host` throwing "The handle is invalid" with no
console attached. That is the runner's environment rather than our command
line, so the round-trip now resolves the verb with Get-Command instead of
running it -- which is also a stronger assertion, because a POSIX `clear`
leaking into the PowerShell branch resolves as an Application on Linux and
would have passed when merely executed. Asserting on the resolved name, not
the command type: Clear-Host is a Function in pwsh 7 but a Cmdlet in Windows
PowerShell 5.

Also removes an over-correction from the previous commit, which required the
clear escape sequences to be present in stdout. Whether Clear-Host emits them
depends on the detected terminal capabilities: they appear with $TERM set and
not on a TTY-less runner, so the requirement failed on CI.

Verified green in all three shapes this affects: with pwsh and $TERM, with
pwsh and $TERM unset (the CI shape, checked with `env -u TERM`), and with no
pwsh at all. 666 passing, 0 failing. Reverting the caret escaping fails 2
tests; reverting the Get-Command change fails 1.

Co-authored-by: Isaac
`read _` is an error in fish, where `_` is a read-only variable
("read: _: cannot overwrite read-only variable", exit 2). That fails the
hold-open step, and the `exit` that follows then closes the terminal and
discards exactly the CLI error the pause exists to keep readable.

There is no shorter spelling that works everywhere:

  candidate     sh  bash  zsh  dash  fish
  read          ok  ok    ok   X     X (echoes the input back)
  read _        ok  ok    ok   ok    X (read-only variable)
  read -r name  ok  ok    ok   ok    X (no -r flag in fish)
  read discard  ok  ok    ok   ok    ok

So use an ordinary variable name. Note `-r` is not the fix it looks like:
fish has no such flag.

fish now takes part in the posix round-trips, probed for on PATH so the
tests skip rather than fail where it isn't installed (neither CI image
has it, nor does macOS out of the box).

Also add a round-trip that asserts the pause consumes its line
*silently*. The existing check only rejects usage errors, which bare
`read` in fish slips past: it exits 0 with empty stderr while echoing the
keypress back into the terminal. Reverting to `read _` fails 6 tests;
reverting to bare `read` fails 7, of which the fish echo-back is caught
only by the new assertion.

Co-authored-by: Isaac
`escapePathArgument`'s posix branch is wrong for fish, which treats `\`
as an escape inside single quotes. `two\\slashes` arrives as
`two\slashes`, and a trailing `\` makes fish report unbalanced quotes.
Both are legal directory names on macOS and Linux, so this is reachable
rather than theoretical.

Not fixed here: fish would need its own ShellKind, since only the quoting
differs and clear/;/printf/read are all shared with posix. That is wider
than the #1822 fix this branch is for, so leave a note where the next
person will look rather than only in the PR description.

Co-authored-by: Isaac
@abdelrahmanomar1515
abdelrahmanomar1515 merged commit ebcf7c9 into main Aug 7, 2026
6 checks passed
@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

If integration tests don't run automatically, an authorized user can run them manually by following the instructions below:

Trigger:
go/deco-tests-run/vscode

Inputs:

  • PR number: 2092
  • Commit SHA: 7af3a578754b39cd067e1edda9ab6de4c9e4236f

Checks will be approved automatically on success.

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.

[BUG] 'clear' is not recognized as an internal or external command

3 participants