Skip to content

fix(core): properly quote shell metacharacters in CLI args passed to tasks#34491

Open
baer wants to merge 2 commits intonrwl:masterfrom
baer:fix/shell-metacharacters-quoting
Open

fix(core): properly quote shell metacharacters in CLI args passed to tasks#34491
baer wants to merge 2 commits intonrwl:masterfrom
baer:fix/shell-metacharacters-quoting

Conversation

@baer
Copy link
Contributor

@baer baer commented Feb 17, 2026

Current Behavior

When CLI arguments containing shell metacharacters (like |, &, $, ;, *, etc.) are passed through Nx to underlying tasks, they are not properly quoted, causing shell interpretation errors.

For example, running:

nx test app --grep="@tag1|@tag2"

Would fail with /bin/sh: @smoke: command not found because the pipe character | was interpreted by the shell as a pipe operator instead of being passed as a literal string to the underlying command.

Users had to use awkward double-quoting workarounds like --grep='"@tag1|@tag2"' to get the expected behavior.

Expected Behavior

CLI arguments containing shell metacharacters should be automatically quoted before being passed to underlying commands, so that:

nx test app --grep="@tag1|@tag2"

Works correctly and passes --grep="@tag1|@tag2" to the underlying test runner without shell interpretation.

Related Issue(s)

Fixes #32305
Fixes #26682

Implementation Details

  • Created a shared needsShellQuoting() utility in packages/nx/src/utils/shell-quoting.ts that detects shell metacharacters
  • Updated wrapArgIntoQuotesIfNeeded() in run-commands.impl.ts to use the shared utility
  • Updated stringShouldBeWrappedIntoQuotes() in serialize-overrides-into-command-line.ts to use the shared utility
  • Fixed a bug where arg.split('=') would incorrectly split values containing = (e.g., --define=FOO=bar|baz)
  • Added proper escaping of embedded double quotes when wrapping values
  • Added comprehensive test coverage

Shell metacharacters now handled:

| & ; < > ( ) $ ` \ " ' * ? [ ] { } ~ # ! and whitespace

@baer baer requested a review from a team as a code owner February 17, 2026 20:13
@baer baer requested a review from leosvelperez February 17, 2026 20:13
@netlify
Copy link

netlify bot commented Feb 17, 2026

Deploy Preview for nx-docs canceled.

Name Link
🔨 Latest commit 2ed5bb2
🔍 Latest deploy log https://app.netlify.com/projects/nx-docs/deploys/69a16ea24c176c0008d76ec6

@netlify
Copy link

netlify bot commented Feb 17, 2026

Deploy Preview for nx-dev canceled.

Name Link
🔨 Latest commit 2ed5bb2
🔍 Latest deploy log https://app.netlify.com/projects/nx-dev/deploys/69a16ea2083cea000889df41

@baer baer force-pushed the fix/shell-metacharacters-quoting branch 2 times, most recently from c3da8e1 to 05e6b74 Compare February 17, 2026 21:18
@nx-cloud
Copy link
Contributor

nx-cloud bot commented Feb 25, 2026

View your CI Pipeline Execution ↗ for commit 2991056

Command Status Duration Result
nx affected --targets=lint,test,build,e2e,e2e-c... ✅ Succeeded 51m 36s View ↗
nx run-many -t check-imports check-lock-files c... ✅ Succeeded 3m 25s View ↗
nx-cloud record -- nx-cloud conformance:check ✅ Succeeded 8s View ↗
nx-cloud record -- nx format:check ✅ Succeeded 2s View ↗
nx-cloud record -- nx sync:check ✅ Succeeded <1s View ↗

☁️ Nx Cloud last updated this comment at 2026-02-27 16:11:33 UTC

jaysoo

This comment was marked as outdated.

@jaysoo jaysoo self-requested a review February 26, 2026 15:49
Copy link
Contributor

@nx-cloud nx-cloud bot left a comment

Choose a reason for hiding this comment

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

Important

At least one additional CI pipeline execution has run since the conclusion below was written and it may no longer be applicable.

Nx Cloud is proposing a fix for your failed CI:

We've updated the shell quoting logic to properly handle values that are already wrapped in quotes. The original implementation only checked if a value started with a quote character, but didn't verify if it was fully wrapped in matching quotes, causing shell parsing errors when literal quoted strings were passed as arguments. This fix detects fully-wrapped single and double quotes, strips double quotes for re-processing, and preserves single quotes as-is, ensuring shell metacharacters are properly escaped while respecting existing quoting conventions.

Warning

We could not verify this fix.

Suggested Fix changes
diff --git a/packages/nx/src/executors/run-commands/run-commands.impl.ts b/packages/nx/src/executors/run-commands/run-commands.impl.ts
index bbbcfa69b9..21f207c674 100644
--- a/packages/nx/src/executors/run-commands/run-commands.impl.ts
+++ b/packages/nx/src/executors/run-commands/run-commands.impl.ts
@@ -398,22 +398,49 @@ function wrapArgIntoQuotesIfNeeded(arg: string): string {
     // Split only on first '=' to handle values containing '='
     const eqIndex = arg.indexOf('=');
     const key = arg.substring(0, eqIndex);
-    const value = arg.substring(eqIndex + 1);
-    if (
-      key.startsWith('--') &&
-      needsShellQuoting(value) &&
-      !(value[0] === "'" || value[0] === '"')
-    ) {
+    let value = arg.substring(eqIndex + 1);
+
+    // Check if value is wrapped in matching quotes
+    if (value.length >= 2) {
+      const firstChar = value[0];
+      const lastChar = value[value.length - 1];
+
+      if (firstChar === "'" && lastChar === "'") {
+        // Preserve single-quoted values as-is
+        return arg;
+      } else if (firstChar === '"' && lastChar === '"') {
+        // Strip outer double quotes and re-process the inner content
+        value = value.substring(1, value.length - 1);
+      }
+    }
+
+    if (key.startsWith('--') && needsShellQuoting(value)) {
       // Escape any existing double quotes in the value
       const escaped = value.replace(/"/g, '\\"');
       return `${key}="${escaped}"`;
     }
-    return arg;
-  } else if (needsShellQuoting(arg) && !(arg[0] === "'" || arg[0] === '"')) {
-    // Escape any existing double quotes in the value
-    const escaped = arg.replace(/"/g, '\\"');
-    return `"${escaped}"`;
+    return `${key}=${value}`;
   } else {
+    // Handle positional arguments
+    // Check if already wrapped in matching quotes
+    if (arg.length >= 2) {
+      const firstChar = arg[0];
+      const lastChar = arg[arg.length - 1];
+
+      if (firstChar === "'" && lastChar === "'") {
+        // Preserve single-quoted values as-is
+        return arg;
+      } else if (firstChar === '"' && lastChar === '"') {
+        // Strip outer double quotes and re-process the inner content
+        arg = arg.substring(1, arg.length - 1);
+      }
+    }
+
+    if (needsShellQuoting(arg)) {
+      // Escape any existing double quotes in the value
+      const escaped = arg.replace(/"/g, '\\"');
+      return `"${escaped}"`;
+    }
     return arg;
   }
 }

🔔 Heads up, your workspace has pending recommendations ↗ to auto-apply fixes for similar failures.

Because this branch comes from a fork, it is not possible for us to apply fixes directly, but you can apply the changes locally using the available options below.

Apply changes locally with:

npx nx-cloud apply-locally iKWx-mv3h

Apply fix locally with your editor ↗   View interactive diff ↗



🎓 Learn more about Self-Healing CI on nx.dev

…tasks

When CLI arguments containing shell metacharacters (like |, &, $, etc.)
were passed through Nx to underlying tasks, they were not properly quoted,
causing shell interpretation errors. For example:

  nx test app --grep="@tag1|@tag2"

Would fail because the pipe character was interpreted by the shell as a
command pipe, resulting in errors like "/bin/sh: @tag2: command not found".

This fix:
- Introduces a new `needsShellQuoting()` utility that detects all shell
  metacharacters: | & ; < > ( ) $ ` \ " ' * ? [ ] { } ~ # ! and whitespace
- Updates `wrapArgIntoQuotesIfNeeded()` to use this utility instead of
  only checking for spaces
- Properly handles values containing `=` by splitting on the first `=` only
- Escapes existing double quotes within values before wrapping
- Adds comprehensive test coverage for the new functionality

Fixes nrwl#32305
Fixes nrwl#26682
@leosvelperez leosvelperez force-pushed the fix/shell-metacharacters-quoting branch from 05e6b74 to 2ed5bb2 Compare February 27, 2026 10:14
…hell quoting

Add isAlreadyQuoted guard to serializeOverridesIntoCommandLine and
wrapArgIntoQuotesIfNeeded to skip re-quoting values already wrapped in
matching quotes.

Add rejoinQuotedFragments to reassemble shell word-split fragments of
quoted values in __unparsed__ before applying quoting logic. Without this,
fragments like args"}}' get incorrectly re-quoted, breaking commands that
pass JSON config via escaped single quotes (e.g., cypress e2e --config).
@leosvelperez leosvelperez force-pushed the fix/shell-metacharacters-quoting branch from 2ed5bb2 to 2991056 Compare February 27, 2026 15:15
@leosvelperez
Copy link
Member

@baer thanks for contributing!

I fixed an issue caught by our e2e test suite and added additional tests to prevent regressions. Could you re-verify on your end that everything still works for you?

@leosvelperez leosvelperez self-assigned this Mar 2, 2026
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.

Quoted CLI flags are not passed through to tasks correctly Playwright --grep flag doesn't work with multiple tags when run via nx command

3 participants