Skip to content

Commit 13d88d2

Browse files
authored
fix: replace xargs trim with sed to handle quotes in descriptions (#2351)
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
1 parent 6bf4ebb commit 13d88d2

3 files changed

Lines changed: 66 additions & 2 deletions

File tree

extensions/git/scripts/bash/create-new-feature.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ if [ -z "$FEATURE_DESCRIPTION" ]; then
9595
fi
9696

9797
# Trim whitespace and validate description is not empty
98-
FEATURE_DESCRIPTION=$(echo "$FEATURE_DESCRIPTION" | xargs)
98+
FEATURE_DESCRIPTION=$(echo "$FEATURE_DESCRIPTION" | sed -E 's/^[[:space:]]+|[[:space:]]+$//g')
9999
if [ -z "$FEATURE_DESCRIPTION" ]; then
100100
echo "Error: Feature description cannot be empty or contain only whitespace" >&2
101101
exit 1

scripts/bash/create-new-feature.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ if [ -z "$FEATURE_DESCRIPTION" ]; then
8484
fi
8585

8686
# Trim whitespace and validate description is not empty (e.g., user passed only whitespace)
87-
FEATURE_DESCRIPTION=$(echo "$FEATURE_DESCRIPTION" | xargs)
87+
FEATURE_DESCRIPTION=$(echo "$FEATURE_DESCRIPTION" | sed -E 's/^[[:space:]]+|[[:space:]]+$//g')
8888
if [ -z "$FEATURE_DESCRIPTION" ]; then
8989
echo "Error: Feature description cannot be empty or contain only whitespace" >&2
9090
exit 1

tests/test_timestamp_branches.py

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1257,3 +1257,67 @@ def test_ps_feature_json_overrides_branch_lookup(self, git_repo: Path):
12571257
break
12581258
else:
12591259
pytest.fail("FEATURE_DIR not found in PowerShell output")
1260+
1261+
1262+
# ── Description Quoting Tests (issue #2339) ──────────────────────────────────
1263+
1264+
1265+
@requires_bash
1266+
class TestDescriptionQuoting:
1267+
"""Descriptions with quotes, apostrophes, and backslashes must not break the script.
1268+
1269+
Regression tests for https://github.com/github/spec-kit/issues/2339
1270+
"""
1271+
1272+
@pytest.mark.parametrize(
1273+
"description",
1274+
[
1275+
"Add user's profile page",
1276+
"Fix the \"login\" bug",
1277+
"Handle path\\with\\backslashes",
1278+
"It's a \"complex\" feature\\here",
1279+
],
1280+
ids=["apostrophe", "double-quotes", "backslashes", "mixed"],
1281+
)
1282+
def test_core_script_handles_special_chars(self, git_repo: Path, description: str):
1283+
"""Core create-new-feature.sh succeeds with special characters in description."""
1284+
result = run_script(git_repo, "--dry-run", "--short-name", "feat", description)
1285+
assert result.returncode == 0, (
1286+
f"Script failed for description {description!r}: {result.stderr}"
1287+
)
1288+
1289+
@pytest.mark.parametrize(
1290+
"description",
1291+
[
1292+
"Add user's profile page",
1293+
"Fix the \"login\" bug",
1294+
"Handle path\\with\\backslashes",
1295+
"It's a \"complex\" feature\\here",
1296+
],
1297+
ids=["apostrophe", "double-quotes", "backslashes", "mixed"],
1298+
)
1299+
def test_ext_script_handles_special_chars(self, ext_git_repo: Path, description: str):
1300+
"""Extension create-new-feature.sh succeeds with special characters in description."""
1301+
script = (
1302+
ext_git_repo / ".specify" / "extensions" / "git" / "scripts" / "bash" / "create-new-feature.sh"
1303+
)
1304+
result = subprocess.run(
1305+
["bash", str(script), "--dry-run", "--short-name", "feat", description],
1306+
cwd=ext_git_repo,
1307+
capture_output=True,
1308+
text=True,
1309+
)
1310+
assert result.returncode == 0, (
1311+
f"Script failed for description {description!r}: {result.stderr}"
1312+
)
1313+
1314+
def test_whitespace_only_still_rejected(self, git_repo: Path):
1315+
"""Whitespace-only descriptions must still be rejected after trimming."""
1316+
result = run_script(git_repo, "--dry-run", "--short-name", "feat", " ")
1317+
assert result.returncode != 0
1318+
assert "empty" in result.stderr.lower() or "whitespace" in result.stderr.lower()
1319+
1320+
def test_plain_description_still_works(self, git_repo: Path):
1321+
"""Plain description without special characters continues to work."""
1322+
result = run_script(git_repo, "--dry-run", "--short-name", "feat", "Add login feature")
1323+
assert result.returncode == 0, result.stderr

0 commit comments

Comments
 (0)