fix: exclusive mkdir so concurrent create-new-feature cannot overwrite spec.md - #4310
fix: exclusive mkdir so concurrent create-new-feature cannot overwrite spec.md#4310BetterAndBetterII wants to merge 1 commit into
Conversation
| # Exclusive create: mkdir without exist_ok fails if another invocation | ||
| # reserved the same FEATURE_DIR after the exists check above. Rescan | ||
| # and retry before writing spec.md so the loser cannot overwrite it. | ||
| while True: |
| # Exclusive create: New-Item without -Force fails if another invocation | ||
| # reserved the same FEATURE_DIR after the exists check above. Rescan | ||
| # and retry before writing spec.md so the loser cannot overwrite it. | ||
| while ($true) { |
|
|
||
|
|
||
| @requires_bash | ||
| @pytest.mark.parametrize("variant", ["bash", "python"]) |
| # Exclusive create: plain mkdir fails with EEXIST if another invocation | ||
| # reserved the same FEATURE_DIR after the exists check above. Rescan | ||
| # and retry before writing spec.md so the loser cannot overwrite it. | ||
| while true; do |
| except FileExistsError: | ||
| if args.allow_existing: | ||
| break |
mnriem
left a comment
There was a problem hiding this comment.
Please address Copilot feedback
Reserve FEATURE_DIR with exclusive mkdir so a lost race rescans instead of clobbering spec.md.
54285a2 to
3a6cc66
Compare
There was a problem hiding this comment.
🔵 Needs a closer look
Pre-loop checks can bypass retries, and allow-existing races can still overwrite spec.md.
Review details
Suppressed comments (7)
scripts/python/create_new_feature.py:400
- A sequential collision can still bypass this retry loop entirely. If another invocation creates
feature_dirafter the number scan but before the existing-directory check at line 370, that check returns an error instead of rescanning. Fold the pre-loop guard into this reservation loop so timestamp collisions remain errors while sequential collisions always retry.
while True:
scripts/bash/create-new-feature.sh:376
- A sequential collision can still bypass this retry loop entirely. If another invocation creates
FEATURE_DIRafter the number scan but before the guard at lines 348-355, the script exits instead of rescanning. Fold that guard into this reservation loop so timestamp collisions remain errors while sequential collisions always retry.
while true; do
tests/test_create_new_feature_python_parity.py:1268
- The forced concurrency regression excludes PowerShell even though this PR adds an independent
New-Item/catch retry path. Add a PowerShell-capable barrier and include a conditionally skipped PowerShell variant so all three first-class script implementations exercise the race.
@pytest.mark.parametrize("variant", ["bash", "python"])
scripts/powershell/create-new-feature.ps1:281
- A sequential collision can still bypass this retry loop entirely. If another invocation creates
$featureDirafter the number scan but before the guard at lines 263-270, the script exits instead of rescanning. Fold that guard into this reservation loop so timestamp collisions remain errors while sequential collisions always retry.
while ($true) {
scripts/python/create_new_feature.py:401
- This allow-existing path can still overwrite a concurrent creator's
spec.md:needs_specis computed before reservation, so another process can create and populate the directory after that check; this process then breaks here and writes based on the staleneeds_spec=True. Track whether this invocation won the reservation, and do not initializespec.mdwhen reusing a directory that appeared concurrently.
if args.allow_existing and feature_dir.is_dir():
scripts/bash/create-new-feature.sh:377
- This allow-existing path can still overwrite a concurrent creator's
spec.md:NEEDS_SPECis computed before reservation, so another process can create and populate the directory after that check; this process then breaks here and writes based on stale state. Track whether this invocation won the reservation, and do not initializespec.mdwhen reusing a directory that appeared concurrently.
if [ "$ALLOW_EXISTING" = true ] && [ -d "$FEATURE_DIR" ]; then
scripts/powershell/create-new-feature.ps1:282
- This allow-existing path can still overwrite a concurrent creator's
spec.md:$needsSpecis computed before reservation, so another process can create and populate the directory after that check; this process then breaks here and writes based on stale state. Track whether this invocation won the reservation, and do not initializespec.mdwhen reusing a directory that appeared concurrently.
if ($AllowExistingBranch -and (Test-Path -LiteralPath $featureDir -PathType Container)) {
- Files reviewed: 4/4 changed files
- Comments generated: 0 new
- Review effort level: Balanced
|
Thanks for the update. The reservation still occurs after existence and Please reserve/retry the directory first, then derive all Posted on behalf of @mnriem by GitHub Copilot (model: GPT-5.6 Sol). |
Problem
create-new-featurepicks the next sequential spec number, checks thatFEATURE_DIRis absent, then creates it withmkdir -p(bash),New-Item -Force(PowerShell), orPath.mkdir(exist_ok=True)(Python). Those creates are not exclusive.Two concurrent invocations can therefore scan the same max number, both pass the exists check, both create the same directory, and the second write clobbers
spec.md.Fix
Create
FEATURE_DIRexclusively. OnEEXIST, rescanspecs/for the next sequential number and retry before writingspec.md. Timestamp mode still errors on collision, matching the previous exists-check behavior.Fixes #4270