From 474fcd250b6cdb6e7c133c3dd7869892997b406e Mon Sep 17 00:00:00 2001 From: Manfred Riem <15701806+mnriem@users.noreply.github.com> Date: Fri, 24 Apr 2026 07:07:13 -0500 Subject: [PATCH] fix: replace xargs trim with sed to handle quotes in descriptions xargs re-parses stdin as shell tokens, causing 'unterminated quote' errors when feature descriptions contain apostrophes, double quotes, or backslashes. Replace with sed-based whitespace trim that preserves input verbatim. Add regression tests for special characters in descriptions (core and extension scripts), plus a negative test for whitespace-only input. Fixes #2339 --- .../git/scripts/bash/create-new-feature.sh | 2 +- scripts/bash/create-new-feature.sh | 2 +- tests/test_timestamp_branches.py | 64 +++++++++++++++++++ 3 files changed, 66 insertions(+), 2 deletions(-) diff --git a/extensions/git/scripts/bash/create-new-feature.sh b/extensions/git/scripts/bash/create-new-feature.sh index 286aaf7634..f7aa31610e 100755 --- a/extensions/git/scripts/bash/create-new-feature.sh +++ b/extensions/git/scripts/bash/create-new-feature.sh @@ -95,7 +95,7 @@ if [ -z "$FEATURE_DESCRIPTION" ]; then fi # Trim whitespace and validate description is not empty -FEATURE_DESCRIPTION=$(echo "$FEATURE_DESCRIPTION" | xargs) +FEATURE_DESCRIPTION=$(echo "$FEATURE_DESCRIPTION" | sed -E 's/^[[:space:]]+|[[:space:]]+$//g') if [ -z "$FEATURE_DESCRIPTION" ]; then echo "Error: Feature description cannot be empty or contain only whitespace" >&2 exit 1 diff --git a/scripts/bash/create-new-feature.sh b/scripts/bash/create-new-feature.sh index 1879647026..c3537704f6 100644 --- a/scripts/bash/create-new-feature.sh +++ b/scripts/bash/create-new-feature.sh @@ -84,7 +84,7 @@ if [ -z "$FEATURE_DESCRIPTION" ]; then fi # Trim whitespace and validate description is not empty (e.g., user passed only whitespace) -FEATURE_DESCRIPTION=$(echo "$FEATURE_DESCRIPTION" | xargs) +FEATURE_DESCRIPTION=$(echo "$FEATURE_DESCRIPTION" | sed -E 's/^[[:space:]]+|[[:space:]]+$//g') if [ -z "$FEATURE_DESCRIPTION" ]; then echo "Error: Feature description cannot be empty or contain only whitespace" >&2 exit 1 diff --git a/tests/test_timestamp_branches.py b/tests/test_timestamp_branches.py index 39228d9455..c99f675081 100644 --- a/tests/test_timestamp_branches.py +++ b/tests/test_timestamp_branches.py @@ -1257,3 +1257,67 @@ def test_ps_feature_json_overrides_branch_lookup(self, git_repo: Path): break else: pytest.fail("FEATURE_DIR not found in PowerShell output") + + +# ── Description Quoting Tests (issue #2339) ────────────────────────────────── + + +@requires_bash +class TestDescriptionQuoting: + """Descriptions with quotes, apostrophes, and backslashes must not break the script. + + Regression tests for https://github.com/github/spec-kit/issues/2339 + """ + + @pytest.mark.parametrize( + "description", + [ + "Add user's profile page", + "Fix the \"login\" bug", + "Handle path\\with\\backslashes", + "It's a \"complex\" feature\\here", + ], + ids=["apostrophe", "double-quotes", "backslashes", "mixed"], + ) + def test_core_script_handles_special_chars(self, git_repo: Path, description: str): + """Core create-new-feature.sh succeeds with special characters in description.""" + result = run_script(git_repo, "--dry-run", "--short-name", "feat", description) + assert result.returncode == 0, ( + f"Script failed for description {description!r}: {result.stderr}" + ) + + @pytest.mark.parametrize( + "description", + [ + "Add user's profile page", + "Fix the \"login\" bug", + "Handle path\\with\\backslashes", + "It's a \"complex\" feature\\here", + ], + ids=["apostrophe", "double-quotes", "backslashes", "mixed"], + ) + def test_ext_script_handles_special_chars(self, ext_git_repo: Path, description: str): + """Extension create-new-feature.sh succeeds with special characters in description.""" + script = ( + ext_git_repo / ".specify" / "extensions" / "git" / "scripts" / "bash" / "create-new-feature.sh" + ) + result = subprocess.run( + ["bash", str(script), "--dry-run", "--short-name", "feat", description], + cwd=ext_git_repo, + capture_output=True, + text=True, + ) + assert result.returncode == 0, ( + f"Script failed for description {description!r}: {result.stderr}" + ) + + def test_whitespace_only_still_rejected(self, git_repo: Path): + """Whitespace-only descriptions must still be rejected after trimming.""" + result = run_script(git_repo, "--dry-run", "--short-name", "feat", " ") + assert result.returncode != 0 + assert "empty" in result.stderr.lower() or "whitespace" in result.stderr.lower() + + def test_plain_description_still_works(self, git_repo: Path): + """Plain description without special characters continues to work.""" + result = run_script(git_repo, "--dry-run", "--short-name", "feat", "Add login feature") + assert result.returncode == 0, result.stderr