Skip to content

Commit 3f14ff4

Browse files
jawwad-aliclaude
andauthored
fix(powershell): stop create-new-feature crashing on a non-Latin description (#4138)
Get-BranchName's fallback assumed the pipeline yields at least one element: $fallbackWords = ($result -split '-') | Where-Object { $_ } | ... return [string]::Join('-', $fallbackWords) ConvertTo-CleanBranchName blanks every non-[a-z0-9] character, so a description written in any non-Latin script leaves nothing for the pipeline to emit. It yields $null, [string]::Join throws ArgumentNullException, and $ErrorActionPreference = 'Stop' makes that terminating — the script dies with a .NET stack trace, empty stdout, exit 1. Measured with all three twins installed in one project: desc='добавить авторизацию' bash rc=0 BRANCH_NAME='001-' py rc=0 BRANCH_NAME='001-' ps rc=1 Exception calling "Join" ... Identical for 添加用户认证 and '!!! ??? ***'. This hits every PowerShell user who phrases a feature in their own language. Wrap the pipeline in @() so it stays an array; Join on an empty array returns "", matching the twins. Verified the normal case is unchanged ('a-to-the-of' -> 'a-to-the', 'add user authentication' -> '001-user-authentication'), and the file stays ASCII-only (0 non-ASCII bytes) for tests/test_ps1_encoding.py. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent cc505da commit 3f14ff4

2 files changed

Lines changed: 61 additions & 1 deletion

File tree

‎scripts/powershell/create-new-feature.ps1‎

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,14 @@ function Get-BranchName {
162162
} else {
163163
# Fallback to original logic if no meaningful words found
164164
$result = ConvertTo-CleanBranchName -Name $Description
165-
$fallbackWords = ($result -split '-') | Where-Object { $_ } | Select-Object -First 3
165+
# @() keeps this an array. ConvertTo-CleanBranchName blanks every
166+
# non-[a-z0-9] character, so a description written in a non-Latin script
167+
# (or made only of punctuation) leaves nothing for the pipeline to
168+
# emit -- it yields $null, and [string]::Join on $null throws
169+
# ArgumentNullException. With $ErrorActionPreference = 'Stop' that is
170+
# terminating, so the script died with a .NET stack trace and exit 1
171+
# where the bash and Python twins both return an empty suffix.
172+
$fallbackWords = @(($result -split '-') | Where-Object { $_ } | Select-Object -First 3)
166173
return [string]::Join('-', $fallbackWords)
167174
}
168175
}

‎tests/test_create_new_feature_python_parity.py‎

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1065,3 +1065,56 @@ def test_all_variants_corrected_prefix_skips_timestamp_collision(repo: Path) ->
10651065
assert json_stdout(py)["FEATURE_NUM"] == "20260320"
10661066
for result in (bash, ps, py):
10671067
assert "using 20260320 instead" in result.stderr
1068+
1069+
1070+
@pytest.mark.skipif(not HAS_POWERSHELL, reason="no PowerShell available")
1071+
@pytest.mark.parametrize(
1072+
"description",
1073+
["!!! ??? ***", "добавить", "添加用户"],
1074+
ids=["punctuation_only", "cyrillic", "han"],
1075+
)
1076+
def test_powershell_survives_description_with_no_ascii_words(
1077+
tmp_path: Path, description: str
1078+
):
1079+
"""A description with no [a-z0-9] characters must not crash the PS twin.
1080+
1081+
``ConvertTo-CleanBranchName`` blanks every non-ASCII character, so the
1082+
fallback pipeline yields nothing and ``[string]::Join`` received ``$null``
1083+
— an ArgumentNullException, made terminating by
1084+
``$ErrorActionPreference = 'Stop'``. The script died with a .NET stack
1085+
trace and exit 1 where the bash and Python twins both return an empty
1086+
suffix. This fires for any feature phrased in a non-Latin script.
1087+
"""
1088+
repo = _setup_repo(tmp_path)
1089+
1090+
ps = run(ps_cmd(repo, SCRIPT, "-Json", "-DryRun", description), repo)
1091+
1092+
assert ps.returncode == 0, ps.stderr
1093+
assert "ArgumentNullException" not in ps.stderr
1094+
assert "Join" not in ps.stderr
1095+
assert json_stdout(ps)["BRANCH_NAME"] == "001-"
1096+
1097+
1098+
@requires_bash
1099+
@pytest.mark.skipif(not HAS_POWERSHELL, reason="no PowerShell available")
1100+
def test_no_ascii_word_description_matches_across_twins(tmp_path: Path):
1101+
"""All three twins agree on the branch name for such a description."""
1102+
description = "добавить"
1103+
1104+
bash_repo = _setup_repo(tmp_path, "b")
1105+
py_repo = _setup_repo(tmp_path, "p")
1106+
ps_repo = _setup_repo(tmp_path, "s")
1107+
1108+
bash = run(bash_cmd(bash_repo, SCRIPT, "--json", "--dry-run", description), bash_repo)
1109+
py = run(py_cmd(py_repo, SCRIPT, "--json", "--dry-run", description), py_repo)
1110+
ps = run(ps_cmd(ps_repo, SCRIPT, "-Json", "-DryRun", description), ps_repo)
1111+
1112+
assert bash.returncode == py.returncode == ps.returncode == 0, (
1113+
bash.stderr, py.stderr, ps.stderr,
1114+
)
1115+
names = {
1116+
json_stdout(bash)["BRANCH_NAME"],
1117+
json_stdout(py)["BRANCH_NAME"],
1118+
json_stdout(ps)["BRANCH_NAME"],
1119+
}
1120+
assert names == {"001-"}, names

0 commit comments

Comments
 (0)