Skip to content

[Bug] create-new-feature.sh uses ${word^^} (bash 4+) and silently drops acronyms on macOS (bash 3.2) #3927

Open

Description

@AdrianBM96

Summary

create-new-feature.sh (line 231) uses ${word^^}, a bash 4+ uppercase expansion. macOS ships bash 3.2.57 (Apple's last GPLv2 build) and the script fails silently on that line: stderr is full of bad substitution warnings, the script keeps running, and the generated branch name silently drops acronyms that were short (≤2 chars) and uppercase in the original description.

Because the script still emits valid JSON and the resulting branch name is plausible, end users on macOS get a quietly truncated branch without realizing it.

Environment

  • macOS (any current version; bash 3.2.57 has been the system bash on every macOS since 2007)
  • Any git repo initialised with specify init
  • No bash from Homebrew installed (or installed but not first in PATH for the running shell)
$ /bin/bash --version
GNU bash, version 3.2.57(1)-release (arm64-apple-darwin24)

$ bash .specify/scripts/bash/create-new-feature.sh --json --dry-run \
    "Transparencia Art. 50 AI Act: aviso de interaccion con IA"
{"BRANCH_NAME":"001-transparencia-art-act", ...}      # ← missing "AI"

$ bash .specify/scripts/bash/create-new-feature.sh --json --dry-run "AI Act transparency"
{"BRANCH_NAME":"001-act-transparency", ...}           # ← missing "AI" (top-3 cutoff also affected)
bad substitution                                         # (only visible in stderr; the JSON looks fine)

The same inputs against bash 5.x preserve the acronym:

$ bash .specify/scripts/bash/create-new-feature.sh --json --dry-run "AI Act transparency"
{"BRANCH_NAME":"001-ai-act-transparency", ...}        # ← "ai" preserved

Root cause

create-new-feature.sh, line 231:

elif echo "$description" | grep -q "\b${word^^}\b"; then
    # Keep short words if they appear as uppercase in original (likely acronyms)
    meaningful_words+=("$word")
fi

${word^^} is case-conversion syntax introduced in bash 4.0 (2009). It is not available in bash 3.2. On bash 3.2 the line errors out, grep runs with the literal placeholder string, the grep returns non-zero, the elif is false, and the short word is silently dropped from meaningful_words[]. The same ${var^^} / ${var,,} pattern likely exists in other vendored scripts; I have not audited them all.

The shebang #!/usr/bin/env bash does not protect against this — env picks whatever bash resolves to in the current PATH, and on a stock macOS that is the system's bash 3.2.

Suggested fix

Two options, in order of preference:

  1. POSIX-portable — replace ${word^^} with a portable uppercase conversion, e.g. echo "$word" | tr '[:lower:]' '[:upper:]', so the script works on bash 3.2 too. This is a one-line change per occurrence and removes the silent failure mode entirely.
  2. Bash 4+ required — if the project decides to require bash 4+ intentionally, make the failure loud: at the top of every vendored script, add a runtime check that fails fast with a clear message and a hint to install bash from Homebrew (brew install bash). Currently the failure is silent and the user has no way to know their branch name is wrong.

I'd recommend option 1 (POSIX-portable) — the function is a small, isolated transformation and the project already has a clean_branch_name that uses tr '[:upper:]' '[:lower:]' to do the inverse. Symmetry argues for replacing the bash-only expansion with tr as well.

Workaround on macOS today

Install bash 5.x via Homebrew and ensure it is first in PATH for the shell that runs Spec-Kit:

brew install bash
# ~/.zshrc or ~/.bashrc, idempotent:
case "$PATH" in
  /opt/homebrew/bin:*) ;;
  /opt/homebrew/bin) ;;
  *) export PATH="/opt/homebrew/bin${PATH:+:$PATH}" ;;
esac

Note that on login shells, /etc/zprofile runs path_helper which re-derives PATH from /etc/paths + /etc/paths.d/* and puts /opt/homebrew/bin at the end — so the prefix must be re-asserted in a file that runs after /etc/zprofile (e.g. ~/.zshrc).

Impact

This affects every macOS developer or agent using Spec-Kit with a description containing acronyms (e.g. "AI Act", "RGPD", "GDPR", "API", "OAuth", "HIPAA", "PCI"). The "feature name" used downstream in specs/NNN-short-name/spec.md is the same truncated string, so the spec file itself also loses the acronym, and any tooling that relies on the branch/spec name to understand the feature (e.g. CI labels, dashboards, search) gets the wrong subject.

Discovered while piloting Spec-Kit on ada-legal-ai (PR #11 in the HOILAB org). Same root cause likely affects any other team running Spec-Kit on macOS.

Activity

  1. github-actions commented on Jul 31, 2026

    @github-actions
    Contributor

    Bug assessment — bash32-acronym-drop: Invalid · severity low (already fixed in codebase)


    Bug Assessment: bash 3.2 ${word^^} drops acronyms in branch names

    Report (summarized)

    Reporter (@AdrianBM96) observes that create-new-feature.sh uses ${word^^} (bash 4+ case-expansion), which fails silently on macOS bash 3.2.57. The failure causes short uppercase acronyms (e.g. "AI", "API", "GDPR") to be dropped from the generated branch name without any visible error to the user.

    Reproduction shows "AI Act transparency" producing "001-act-transparency" instead of "001-ai-act-transparency" on macOS bash 3.2.

    Symptom

    On macOS (stock bash 3.2), ${word^^} emits a bad substitution error to stderr; the acronym-retention elif branch evaluates to false; short uppercase acronyms are silently dropped from the generated branch name and downstream spec filename.

    Reproduction

    1. macOS with stock /bin/bash (3.2.57), no Homebrew bash in PATH.
    2. Run: bash .specify/scripts/bash/create-new-feature.sh --json --dry-run "AI Act transparency"
    3. Expected: BRANCH_NAME contains ai-act-transparency.
    4. Actual (before fix): BRANCH_NAME is act-transparency; bad substitution on stderr.

    Suspected Code Paths

    • scripts/bash/create-new-feature.sh:219 — the acronym-retention elif — this is where the fix was applied.

    Root Cause Hypothesis

    ${word^^} (bash 4+ uppercase expansion) was used to test whether a short word appeared in uppercase in the original description. bash 3.2 does not support this syntax; the line errored, grep received a malformed pattern, returned non-zero, and the word was silently skipped. Confidence: high (confirmed by code history and PR #3192).

    Already Fixed

    This bug was addressed in commit 86709f6 (merged as PR #3192: fix(scripts): portable uppercase for branch-name acronym retention (bash 3.2)).

    The current code at scripts/bash/create-new-feature.sh line 219 reads:

    elif printf '%s' "$description" | grep -qw -- "$(printf '%s' "$word" | tr '[:lower:]' '[:upper:]')"; then

    The comment at line 217–218 even explicitly documents the rationale:

    Uppercase via tr and match with grep -w (both portable) rather than bash's 4+ "^^" case expansion (breaks on macOS bash 3.2) and \b (non-POSIX).

    The fix uses tr '[:lower:]' '[:upper:]' — the POSIX-portable approach the reporter themselves recommended as "option 1".

    Proposed Remediation

    No change needed in the current main branch — the fix is already in place.

    Recommendation: Close this issue as "already fixed". If the reporter is running an older version of Spec-Kit, upgrading to the current release (which includes PR #3192) will resolve the issue.

    Files likely to change: none.

    Tests to add or update: PR #3192 presumably added or updated tests; no new work is needed.

    Risks & Considerations

    • No risk — no change required.
    • Reporter may be running a pinned/older version; a release note or version callout in the close comment may be helpful.

    Open Questions

    • Which version of Spec-Kit was the reporter running when they encountered this? If they are on main, the issue would not reproduce.

    Generated by 🐛 Assess Bug from Labeled Issue for issue #3927 · 140.1 AIC · ⌖ 12.3 AIC · ⊞ 34.2K · ◷

  2. mnriem commented on Jul 31, 2026

    @mnriem
    Collaborator

    Can you please address the open questions?

  3. assigned and unassigned on Jul 31, 2026
  4. github-actions commented on Oct 4, 2026

    @github-actions
    Contributor

    This issue has been automatically marked as stale because it has had no activity for 60 days. It will be closed in 30 days unless there is a substantive update. If the problem still applies, please provide updated context such as whether it is still reproducible, the affected version or environment, its current impact, and any new reproduction details. A comment that only states that the issue is "still relevant" does not give maintainers enough information to reconsider it.

  5. added
    triage-out-of-scopeVerdict: won't land in core — invalid, duplicate, off-mission, or redirected to an extension
    on Oct 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bug-assessinvalidThis doesn't seem righttriage-out-of-scopeVerdict: won't land in core — invalid, duplicate, off-mission, or redirected to an extension

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions