From 62d91b9f0aecbad3d04c52f08451bf195fb5b50a Mon Sep 17 00:00:00 2001 From: Johnny Winn Date: Mon, 27 Jul 2026 13:15:03 -0600 Subject: [PATCH] fix: config:get --shell does not double-escape backslashes in single-quoted output MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit POSIX single-quoted strings treat all characters literally — backslashes are NOT special and must NOT be escaped. The previous implementation ran s.replaceAll(/(['\\])/g, String.raw\`\$1\`) on the value before wrapping it in single quotes, so a literal backslash in a config value (e.g. foo\nbar) was emitted as 'foo\\nbar', which the shell would interpret as two characters, not one. Because the single-quote branch is only reached when the string contains no single quotes and no newlines, there are no special characters to escape at all. The fix removes the replaceAll call entirely (src/lib/config/quote.ts:12). The corresponding parse() branch had a compensating replaceAll('\\\\', '\\') to undo the double-escaping; that is also removed since it is no longer needed and would incorrectly modify strings that genuinely contain two consecutive backslashes (src/lib/config/quote.ts:22). Fixes #1384 (W-23597884) Co-Authored-By: Claude Sonnet 4.6 (1M context) --- src/lib/config/quote.ts | 4 ++-- test/unit/lib/config/quote.test.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib/config/quote.ts b/src/lib/config/quote.ts index 7126fbfdb9..769dd24e0b 100644 --- a/src/lib/config/quote.ts +++ b/src/lib/config/quote.ts @@ -9,7 +9,7 @@ export function quote(s: string): string { .replaceAll(/(["\\$`!])/g, String.raw`\$1`) .replaceAll('\n', String.raw`\n`) + '"' - return "'" + s.replaceAll(/(['\\])/g, String.raw`\$1`) + "'" + return "'" + s + "'" } return s @@ -19,7 +19,7 @@ export function parse(a: string): string { if (a.startsWith('"')) { a = a.replaceAll(String.raw`\n`, '\n') } else if (a.startsWith("'")) { - a = a.replaceAll('\\\\', '\\') + // single-quoted strings are literal in POSIX sh — no unescaping needed } const parsed = shell.parse(a) diff --git a/test/unit/lib/config/quote.test.ts b/test/unit/lib/config/quote.test.ts index 5d51474ea5..2d251e7f2a 100644 --- a/test/unit/lib/config/quote.test.ts +++ b/test/unit/lib/config/quote.test.ts @@ -8,7 +8,7 @@ describe('quote', function () { ['ab$c', "'ab$c'"], ['a\'bc', '"a\'bc"'], ['a\nb\nc', String.raw`"a\nb\nc"`], - [String.raw`foo\nb:ar\bz`, String.raw`'foo\\nb:ar\\bz'`], + [String.raw`foo\nb:ar\bz`, String.raw`'foo\nb:ar\bz'`], ]) { it(`${a}===${b}`, function () { expect(quote(a)).to.eq(b)