Skip to content

fix(scripts): validate --number flag against existing branches/specs#2036

Open
fsilvaortiz wants to merge 9 commits intogithub:mainfrom
fsilvaortiz:fix/validate-branch-number-collision
Open

fix(scripts): validate --number flag against existing branches/specs#2036
fsilvaortiz wants to merge 9 commits intogithub:mainfrom
fsilvaortiz:fix/validate-branch-number-collision

Conversation

@fsilvaortiz
Copy link
Copy Markdown
Contributor

Summary

Fixes the intermittent branch numbering reset reported in #1744 (see comment by @echarrod).

PR #1757 fixed the specify.md prompt to tell the AI not to pass --number, but AI assistants intermittently ignore that instruction, causing new features to get 001 instead of the next available number.

This PR adds script-level validation as defense in depth:

  • When --number is passed, the script checks specs directories and git branches (after fetch) for collisions
  • If the number is already in use, it warns on stderr and auto-detects the next available number
  • Skips validation when --allow-existing-branch is set (intentional reuse)
  • Applied symmetrically to both Bash and PowerShell scripts

Test plan

  • All 942 existing tests pass
  • Updated test_without_flag_still_errorstest_without_flag_auto_detects_on_collision to reflect new behavior
  • Verified collision detection against specs directories
  • Verified collision detection against git branches (local + remote)
  • Verified --allow-existing-branch bypasses validation correctly
  • Bash syntax validated

🤖 Generated with Claude Code

Co-Authored-By: Claude Opus 4.6 (1M context) noreply@anthropic.com

When an AI assistant ignores the prompt instruction to omit --number
and passes a conflicting value (e.g. --number 1), the script now
detects the collision and auto-detects the next available number
instead of silently reusing it. This prevents branch numbering
from resetting to 001 — the root cause of github#1744.

- Check specs directories and git branches (after fetch) for collisions
- Warn on stderr and fall back to auto-detection on conflict
- Skip validation when --allow-existing-branch is set (intentional reuse)
- Applied symmetrically to both Bash and PowerShell scripts

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds defense-in-depth in the feature-creation scripts to prevent branch/spec number collisions when callers manually pass a number, addressing intermittent “reset to 001” behavior when AI assistants ignore guidance and supply --number.

Changes:

  • Bash: when --number is provided (and --allow-existing-branch is not), detect collisions against existing spec directories and git branches (after fetch) and auto-select the next available number.
  • PowerShell: implement the same collision validation + auto-detect behavior for -Number, with -AllowExistingBranch bypass.
  • Tests: update the existing “no allow-existing” test to assert the new auto-detect-on-collision behavior.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
scripts/bash/create-new-feature.sh Validates manually provided --number against existing spec dirs/branches; auto-detects next number on collision.
scripts/powershell/create-new-feature.ps1 Mirrors the same manual number collision validation and auto-detect logic for PowerShell users.
tests/test_timestamp_branches.py Updates the relevant regression test to reflect the new collision-handling behavior.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
Copy Markdown
Collaborator

@mnriem mnriem left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please address Copilot feedback. If not applicable, please explain why

Addresses Copilot review comment — verifies the PowerShell script
contains the collision detection logic (manualNumPadded, git fetch,
warning message, AllowExistingBranch bypass).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@fsilvaortiz
Copy link
Copy Markdown
Contributor Author

Addressed Copilot suggestion in e602275. Added a static guard test (test_powershell_has_number_collision_validation) that verifies the PS script contains the collision detection logic — matching the existing pattern used for PowerShell tests in this file, since pwsh availability is not guaranteed in CI.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

…rtion

- Bash: reject non-numeric --number with clear error before arithmetic
- PowerShell: reject negative -Number values (< 1) with clear error
- Test: use regex for elseif assertion to tolerate whitespace changes

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
fsilvaortiz and others added 2 commits March 31, 2026 18:08
- Bash: reject non-numeric and zero --number before if/elif (fixes
  crash with --allow-existing-branch + non-numeric --number)
- PowerShell: reject negative -Number before if/elseif (same issue)
- Ensures consistent behavior: 0 is rejected in Bash (PS already
  treats 0 as auto-detect)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Reject --number 0, non-numeric, and negative values
- Detect collision via specs directory (not just branches)
- Accept unused --number as-is
- Static guard: PS script rejects negative -Number

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@mnriem mnriem self-requested a review March 31, 2026 21:12
@fsilvaortiz
Copy link
Copy Markdown
Contributor Author

Re: Copilot comment about --number 0 — already addressed in 59153f7. The validation was moved before the if/elif block and now rejects 0 explicitly (-lt 1 check). Test coverage added in 6cf7bd1 (test_rejects_zero).

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
Copy Markdown
Collaborator

@mnriem mnriem left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One more small thing according to Copilot

…ault

Addresses Copilot review — previously -Number 0 silently fell through
to auto-detect. Now uses $PSBoundParameters.ContainsKey('Number') to
reject explicitly passed values < 1, matching Bash behavior.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@mnriem
Copy link
Copy Markdown
Collaborator

mnriem commented Mar 31, 2026

@fsilvaortiz So more Copilot feedback to address

Avoids double fetch when collision is detected — check_existing_branches
already fetches internally during auto-detect fallback. Matches the
PowerShell script change from previous commit.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@fsilvaortiz
Copy link
Copy Markdown
Contributor Author

Addressed the double-fetch comments in 135163b (bash) and a8e710e (PowerShell): removed git fetch --all --prune from the collision check block. If a collision is detected, check_existing_branches/Get-NextBranchNumber already fetch internally during auto-detect fallback. The collision check uses git branch -a with whatever refs are currently available.

Matches bash change from 135163b — the collision check now uses
git branch -a with available refs. If a collision triggers auto-detect,
Get-NextBranchNumber fetches internally. Updates static guard to match.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

- grep -Eq instead of grep -q with non-POSIX \+ (portability fix)
- Restore git fetch before collision check in both scripts:
  correctness over performance — without fetch, remote-only branches
  are invisible and collisions go undetected

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +305 to +308
if [ "$NUMBER_IN_USE" = true ]; then
>&2 echo "Warning: --number $BRANCH_NUMBER conflicts with existing branch/spec (${MANUAL_NUM_PADDED}-*). Auto-detecting next available number."
if [ "$HAS_GIT" = true ]; then
BRANCH_NUMBER=$(check_existing_branches "$SPECS_DIR")
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When a collision is detected, this branch re-calls check_existing_branches, which itself runs git fetch --all --prune. Since the collision check above already fetched and scanned git branch -a, this can cause a redundant second fetch (extra latency / potential network hang) in the exact failure mode this PR is trying to harden. Consider refactoring so the conflict path reuses the already-fetched branch/spec info (or add a way for check_existing_branches to skip fetching when the caller already fetched).

Suggested change
if [ "$NUMBER_IN_USE" = true ]; then
>&2 echo "Warning: --number $BRANCH_NUMBER conflicts with existing branch/spec (${MANUAL_NUM_PADDED}-*). Auto-detecting next available number."
if [ "$HAS_GIT" = true ]; then
BRANCH_NUMBER=$(check_existing_branches "$SPECS_DIR")
compute_next_branch_number_from_cache() {
# Compute the next available branch number using existing specs and an
# already-fetched list of git branches (to avoid a redundant git fetch).
local specs_dir="$1"
local branches_input="$2"
local highest max_num
# Start from the highest number found in the specs directory.
highest=$(get_highest_from_specs "$specs_dir")
max_num="$highest"
# If we have a cached branch list, incorporate it to find the true max.
if [ -n "$branches_input" ]; then
while IFS= read -r branch; do
# Normalize branch name: strip leading markers and remote prefix.
local clean_branch
clean_branch=$(echo "$branch" | sed 's/^[* ]*//; s|^remotes/[^/]*/||')
# Extract numeric prefix (e.g., "012" from "012-something").
local num_part
num_part=${clean_branch%%-*}
# Ensure prefix is numeric before comparing.
if echo "$num_part" | grep -Eq '^[0-9]+$'; then
local num_value
num_value=$((10#$num_part))
if [ "$num_value" -gt "$max_num" ]; then
max_num="$num_value"
fi
fi
done <<< "$branches_input"
fi
echo $((max_num + 1))
}
if [ "$NUMBER_IN_USE" = true ]; then
>&2 echo "Warning: --number $BRANCH_NUMBER conflicts with existing branch/spec (${MANUAL_NUM_PADDED}-*). Auto-detecting next available number."
if [ "$HAS_GIT" = true ]; then
if [ -n "${branches:-}" ]; then
# Reuse the already-fetched branch list to avoid a redundant git fetch.
BRANCH_NUMBER=$(compute_next_branch_number_from_cache "$SPECS_DIR" "$branches")
else
# No cached branches available; fall back to the existing behavior.
BRANCH_NUMBER=$(check_existing_branches "$SPECS_DIR")
fi

Copilot uses AI. Check for mistakes.
Comment on lines +255 to +260
if ($numberInUse) {
Write-Warning "-Number $Number conflicts with existing branch/spec ($manualNumPadded-*). Auto-detecting next available number."
if ($hasGit) {
$Number = Get-NextBranchNumber -SpecsDir $specsDir
} else {
$Number = (Get-HighestNumberFromSpecs -SpecsDir $specsDir) + 1
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the collision path, the script has already executed git fetch --all --prune (above) to validate $Number, but then calls Get-NextBranchNumber, which performs another git fetch --all --prune. This duplicates network work and can add noticeable latency/hangs when the user hits a collision (the scenario this validation is targeting). Consider reusing the already-fetched branch/spec scan results or adding a -SkipFetch option to Get-NextBranchNumber for callers that already fetched.

Copilot uses AI. Check for mistakes.
@fsilvaortiz
Copy link
Copy Markdown
Contributor Author

Re: double fetch optimization suggestions (comments on bash line 308 and PS line 260):

Acknowledged — the collision-then-auto-detect path does fetch twice. This is intentional and accepted as a trade-off:

  1. Correctness first: The collision check needs fresh remote refs to avoid false negatives. The auto-detect fallback (check_existing_branches/Get-NextBranchNumber) fetches internally and we cannot skip that without modifying existing function signatures.

  2. Rare path: The double fetch only happens when --number is passed AND collides — the exact scenario this PR defends against. In normal operation (no --number flag), the existing single-fetch path is unchanged.

  3. Scope: Introducing a compute_next_branch_number_from_cache() function or adding a -SkipFetch parameter to Get-NextBranchNumber would expand this PR beyond its fix scope and modify stable interfaces. If the maintainer feels this optimization is valuable, it can be a follow-up PR.

The first comment (PS missing fetch) was already addressed in 29302c2.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants