Conversation
The minor version bump was using $$((NF-1)) on the right-hand side of the awk assignment, which was being evaluated as a shell arithmetic expression rather than an awk field reference. Replace with $$(NF-1) to consistently use awk field reference syntax on both sides.
📝 WalkthroughWalkthroughMakefile version targets replaced multi-line git-tag pipelines with explicit shell variable extraction (LATEST_TAG, NEW_TAG), write NEW_TAG to Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/Makefile`:
- Around line 150-158: The release recipes (minor-release and major-release)
currently push all local tags via git push 'refs/tags/v*:refs/tags/v*'; change
them to push only the specific tag produced by the increment targets by passing
the computed NEW_TAG back to the release target—e.g., have
increment-minor-version / increment-major-version write NEW_TAG to a temporary
file (or echo it so the parent recipe runs in the same shell) and then use that
value in minor-release / major-release to run git push origin refs/tags/$(shell
cat NEW_TAG_FILE) or git push origin refs/tags/$(NEW_TAG) instead of the v*
glob; update the Makefile targets increment-minor-version,
increment-major-version, minor-release, and major-release to implement this flow
and ensure the temp file is cleaned up after push.
- Around line 129-146: The targets that compute NEW_TAG
(increment-patch-version, increment-minor-version, increment-major-version)
assume LATEST_TAG exists; add a guard immediately after computing LATEST_TAG
that checks if it's empty and, if so, prints an error to stderr and exits with
non‑zero status to fail fast. Specifically, in each target (where LATEST_TAG and
NEW_TAG are set) check LATEST_TAG (the variable created by git tag -l 'v*'
--sort=-v:refname | head -n1) and call something like echo "No existing v* tags
found" >&2; exit 1 before running the awk and git tag steps so you avoid
creating invalid tags when no prior release exists. Ensure the guard is added in
the anonymous first block (patch), increment-minor-version, and
increment-major-version blocks referencing LATEST_TAG and NEW_TAG.
- Around line 129-146: The recipe lines that compute and print NEW_TAG (the
multi-line shell commands assigning LATEST_TAG/NEW_TAG and running git tag) are
being echoed by make; prefix each recipe line with @ to suppress command echoing
so the targets produce clean output—apply this to the unnamed tag-increment
target (the block that sets LATEST_TAG/NEW_TAG and runs git tag), the
increment-minor-version target, and the increment-major-version target, keeping
the existing tab indentation and preserving the exact shell commands
(LATEST_TAG=..., NEW_TAG=..., echo $$NEW_TAG; git tag $$NEW_TAG).
src/Makefile
Outdated
| LATEST_TAG=$$(git tag -l 'v*' --sort=-v:refname | head -n1); \ | ||
| NEW_TAG=$$(echo $$LATEST_TAG | awk -F. '{$$NF = $$NF + 1;} 1' OFS=.); \ | ||
| echo $$NEW_TAG; \ | ||
| git tag $$NEW_TAG | ||
|
|
||
| .PHONY: increment-minor-version | ||
| increment-minor-version: | ||
| @git tag $$( \ | ||
| git tag -l 'v*' --sort=-v:refname | \ | ||
| head -n1 | \ | ||
| awk -F. '{$$(NF-1) = $$((NF-1)) + 1; $$NF = 0;} 1' OFS=. \ | ||
| ) | ||
| LATEST_TAG=$$(git tag -l 'v*' --sort=-v:refname | head -n1); \ | ||
| NEW_TAG=$$(echo $$LATEST_TAG | awk -F. '{$$(NF-1) = $$(NF-1) + 1; $$NF = 0;} 1' OFS=.); \ | ||
| echo $$NEW_TAG; \ | ||
| git tag $$NEW_TAG | ||
|
|
||
| .PHONY: increment-major-version | ||
| increment-major-version: | ||
| @git tag $$( \ | ||
| git tag -l 'v*' --sort=-v:refname | \ | ||
| head -n1 | \ | ||
| awk -F. '{maj=substr($$1,2)+1; printf "v%d.0.0\n", maj}' \ | ||
| ) | ||
| LATEST_TAG=$$(git tag -l 'v*' --sort=-v:refname | head -n1); \ | ||
| NEW_TAG=$$(echo $$LATEST_TAG | awk -F. '{maj=substr($$1,2)+1; printf "v%d.0.0\n", maj}'); \ | ||
| echo $$NEW_TAG; \ | ||
| git tag $$NEW_TAG |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
head -200 src/Makefile | tail -80Repository: DefangLabs/defang
Length of output: 2452
🏁 Script executed:
wc -l src/Makefile && sed -n '129,146p' src/MakefileRepository: DefangLabs/defang
Length of output: 756
🏁 Script executed:
#!/bin/bash
# Simulate the behavior when no tags match
LATEST_TAG=$$(git tag -l 'v*' --sort=-v:refname | head -n1)
echo "LATEST_TAG value: '${LATEST_TAG}'"
echo "LATEST_TAG is empty: $([ -z "$LATEST_TAG" ] && echo 'yes' || echo 'no')"
# Test what happens when we try to use the empty tag in the awk command
echo "Testing awk with empty LATEST_TAG:"
NEW_TAG=$$(echo ${LATEST_TAG} | awk -F. '{$$NF = $$NF + 1;} 1' OFS=.)
echo "NEW_TAG result: '${NEW_TAG}'"Repository: DefangLabs/defang
Length of output: 207
🏁 Script executed:
#!/bin/bash
# Test if the proposed guard syntax works in shell (which Makefile uses)
cat > /tmp/test_guard.sh << 'EOF'
#!/bin/bash
LATEST_TAG=""
test -n "$LATEST_TAG" || { echo "no existing v* tag found" >&2; exit 1; }
echo "Tag exists: $LATEST_TAG"
EOF
chmod +x /tmp/test_guard.sh
echo "Testing guard with empty tag:"
/tmp/test_guard.sh || echo "Guard correctly exited with code $?"
# Test with non-empty tag
cat > /tmp/test_guard2.sh << 'EOF'
#!/bin/bash
LATEST_TAG="v1.0.0"
test -n "$LATEST_TAG" || { echo "no existing v* tag found" >&2; exit 1; }
echo "Tag exists: $LATEST_TAG"
EOF
chmod +x /tmp/test_guard2.sh
echo -e "\nTesting guard with valid tag:"
/tmp/test_guard2.shRepository: DefangLabs/defang
Length of output: 217
🏁 Script executed:
git tag -l 'v*' --sort=-v:refname | head -5Repository: DefangLabs/defang
Length of output: 43
🏁 Script executed:
#!/bin/bash
# Simulate empty LATEST_TAG and see what error the awk command produces
LATEST_TAG=""
echo "Testing patch-version logic with empty LATEST_TAG:"
NEW_TAG=$(echo $LATEST_TAG | awk -F. '{$NF = $NF + 1;} 1' OFS=.) 2>&1
echo "Result: '$NEW_TAG'"
# Test with minor version
echo -e "\nTesting minor-version logic with empty LATEST_TAG:"
NEW_TAG=$(echo $LATEST_TAG | awk -F. '{$(NF-1) = $(NF-1) + 1; $NF = 0;} 1' OFS=.) 2>&1
echo "Result: '$NEW_TAG'"
# Test with major version
echo -e "\nTesting major-version logic with empty LATEST_TAG:"
NEW_TAG=$(echo $LATEST_TAG | awk -F. '{maj=substr($1,2)+1; printf "v%d.0.0\n", maj}') 2>&1
echo "Result: '$NEW_TAG'"Repository: DefangLabs/defang
Length of output: 343
🏁 Script executed:
#!/bin/bash
# Verify the guard syntax works in a Makefile context
# by testing the actual escape sequence as it appears in Makefile
cat > /tmp/test_makefile.mk << 'EOF'
.PHONY: test-guard
test-guard:
LATEST_TAG=$$(git tag -l 'v*' --sort=-v:refname | head -n1); \
test -n "$$LATEST_TAG" || { echo "no existing v* tag found" >&2; exit 1; }; \
echo "Tag found: $$LATEST_TAG"
.PHONY: test-guard-fail
test-guard-fail:
LATEST_TAG=""; \
test -n "$$LATEST_TAG" || { echo "no existing v* tag found" >&2; exit 1; }; \
echo "Tag found: $$LATEST_TAG"
EOF
echo "Testing Makefile guard syntax (will find no tags, so guard should trigger):"
make -f /tmp/test_makefile.mk test-guard 2>&1 || echo "Expected failure"
echo -e "\nTesting Makefile guard with explicitly empty tag:"
make -f /tmp/test_makefile.mk test-guard-fail 2>&1 || echo "Expected failure"Repository: DefangLabs/defang
Length of output: 309
🏁 Script executed:
#!/bin/bash
# Simulate the exact shell commands as they would be expanded in Makefile
# Test what happens with the current code (without guard)
echo "=== Current behavior (without guard) ==="
echo -e "\n1. Patch version with no tags:"
LATEST_TAG=$(git tag -l 'v*' --sort=-v:refname | head -n1)
echo "LATEST_TAG: '$LATEST_TAG'"
NEW_TAG=$(echo $LATEST_TAG | awk -F. '{$NF = $NF + 1;} 1' OFS=.)
echo "NEW_TAG produced: '$NEW_TAG'"
echo "Would create tag: 'v$NEW_TAG' (INCORRECT - should fail)"
echo -e "\n2. Minor version with no tags:"
LATEST_TAG=$(git tag -l 'v*' --sort=-v:refname | head -n1)
echo "LATEST_TAG: '$LATEST_TAG'"
NEW_TAG=$(echo $LATEST_TAG | awk -F. '{$(NF-1) = $(NF-1) + 1; $NF = 0;} 1' OFS=.) 2>&1
echo "NEW_TAG produced: '$NEW_TAG'"
echo "Error occurred: YES"
echo -e "\n3. Major version with no tags:"
LATEST_TAG=$(git tag -l 'v*' --sort=-v:refname | head -n1)
echo "LATEST_TAG: '$LATEST_TAG'"
NEW_TAG=$(echo $LATEST_TAG | awk -F. '{maj=substr($1,2)+1; printf "v%d.0.0\n", maj}')
echo "NEW_TAG produced: '$NEW_TAG'"
echo "Would create tag: '$NEW_TAG' (INCORRECT - should fail)"
echo -e "\n=== With proposed guard ==="
echo -e "\n4. With guard (empty LATEST_TAG):"
LATEST_TAG=""
test -n "$LATEST_TAG" && echo "Would proceed" || { echo "Guard triggered: no existing v* tag found"; exit 1; }Repository: DefangLabs/defang
Length of output: 628
Add guard to fail fast when no matching release tag exists.
When git tag -l 'v*' returns nothing, LATEST_TAG is empty, causing real problems:
increment-patch-versionsilently creates tagv1(incorrect)increment-minor-versioncrashes withawk: attempt to access field -1increment-major-versionsilently creates tagv1.0.0(incorrect)
Add the guard to all three targets:
Proposed fix
increment-patch-version:
LATEST_TAG=$$(git tag -l 'v*' --sort=-v:refname | head -n1); \
+ test -n "$$LATEST_TAG" || { echo "no existing v* tag found" >&2; exit 1; }; \
NEW_TAG=$$(echo $$LATEST_TAG | awk -F. '{$$NF = $$NF + 1;} 1' OFS=.); \
echo $$NEW_TAG; \
git tag $$NEW_TAG
increment-minor-version:
LATEST_TAG=$$(git tag -l 'v*' --sort=-v:refname | head -n1); \
+ test -n "$$LATEST_TAG" || { echo "no existing v* tag found" >&2; exit 1; }; \
NEW_TAG=$$(echo $$LATEST_TAG | awk -F. '{$$(NF-1) = $$(NF-1) + 1; $$NF = 0;} 1' OFS=.); \
echo $$NEW_TAG; \
git tag $$NEW_TAG
increment-major-version:
LATEST_TAG=$$(git tag -l 'v*' --sort=-v:refname | head -n1); \
+ test -n "$$LATEST_TAG" || { echo "no existing v* tag found" >&2; exit 1; }; \
NEW_TAG=$$(echo $$LATEST_TAG | awk -F. '{maj=substr($$1,2)+1; printf "v%d.0.0\n", maj}'); \
echo $$NEW_TAG; \
git tag $$NEW_TAG📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| LATEST_TAG=$$(git tag -l 'v*' --sort=-v:refname | head -n1); \ | |
| NEW_TAG=$$(echo $$LATEST_TAG | awk -F. '{$$NF = $$NF + 1;} 1' OFS=.); \ | |
| echo $$NEW_TAG; \ | |
| git tag $$NEW_TAG | |
| .PHONY: increment-minor-version | |
| increment-minor-version: | |
| @git tag $$( \ | |
| git tag -l 'v*' --sort=-v:refname | \ | |
| head -n1 | \ | |
| awk -F. '{$$(NF-1) = $$((NF-1)) + 1; $$NF = 0;} 1' OFS=. \ | |
| ) | |
| LATEST_TAG=$$(git tag -l 'v*' --sort=-v:refname | head -n1); \ | |
| NEW_TAG=$$(echo $$LATEST_TAG | awk -F. '{$$(NF-1) = $$(NF-1) + 1; $$NF = 0;} 1' OFS=.); \ | |
| echo $$NEW_TAG; \ | |
| git tag $$NEW_TAG | |
| .PHONY: increment-major-version | |
| increment-major-version: | |
| @git tag $$( \ | |
| git tag -l 'v*' --sort=-v:refname | \ | |
| head -n1 | \ | |
| awk -F. '{maj=substr($$1,2)+1; printf "v%d.0.0\n", maj}' \ | |
| ) | |
| LATEST_TAG=$$(git tag -l 'v*' --sort=-v:refname | head -n1); \ | |
| NEW_TAG=$$(echo $$LATEST_TAG | awk -F. '{maj=substr($$1,2)+1; printf "v%d.0.0\n", maj}'); \ | |
| echo $$NEW_TAG; \ | |
| git tag $$NEW_TAG | |
| LATEST_TAG=$$(git tag -l 'v*' --sort=-v:refname | head -n1); \ | |
| test -n "$$LATEST_TAG" || { echo "no existing v* tag found" >&2; exit 1; }; \ | |
| NEW_TAG=$$(echo $$LATEST_TAG | awk -F. '{$$NF = $$NF + 1;} 1' OFS=.); \ | |
| echo $$NEW_TAG; \ | |
| git tag $$NEW_TAG | |
| .PHONY: increment-minor-version | |
| increment-minor-version: | |
| LATEST_TAG=$$(git tag -l 'v*' --sort=-v:refname | head -n1); \ | |
| test -n "$$LATEST_TAG" || { echo "no existing v* tag found" >&2; exit 1; }; \ | |
| NEW_TAG=$$(echo $$LATEST_TAG | awk -F. '{$$(NF-1) = $$(NF-1) + 1; $$NF = 0;} 1' OFS=.); \ | |
| echo $$NEW_TAG; \ | |
| git tag $$NEW_TAG | |
| .PHONY: increment-major-version | |
| increment-major-version: | |
| LATEST_TAG=$$(git tag -l 'v*' --sort=-v:refname | head -n1); \ | |
| test -n "$$LATEST_TAG" || { echo "no existing v* tag found" >&2; exit 1; }; \ | |
| NEW_TAG=$$(echo $$LATEST_TAG | awk -F. '{maj=substr($$1,2)+1; printf "v%d.0.0\n", maj}'); \ | |
| echo $$NEW_TAG; \ | |
| git tag $$NEW_TAG |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/Makefile` around lines 129 - 146, The targets that compute NEW_TAG
(increment-patch-version, increment-minor-version, increment-major-version)
assume LATEST_TAG exists; add a guard immediately after computing LATEST_TAG
that checks if it's empty and, if so, prints an error to stderr and exits with
non‑zero status to fail fast. Specifically, in each target (where LATEST_TAG and
NEW_TAG are set) check LATEST_TAG (the variable created by git tag -l 'v*'
--sort=-v:refname | head -n1) and call something like echo "No existing v* tags
found" >&2; exit 1 before running the awk and git tag steps so you avoid
creating invalid tags when no prior release exists. Ensure the guard is added in
the anonymous first block (patch), increment-minor-version, and
increment-major-version blocks referencing LATEST_TAG and NEW_TAG.
🧩 Analysis chain
🏁 Script executed:
head -n 160 src/Makefile | tail -n +120Repository: DefangLabs/defang
Length of output: 1299
🏁 Script executed:
cat > /tmp/test_makefile << 'EOF'
.PHONY: test1
test1:
echo "line 1"; \
echo "line 2"
.PHONY: test2
test2:
`@echo` "line 1"; \
echo "line 2"
.PHONY: test3
test3:
`@echo` "line 1"; \
`@echo` "line 2"
EOF
echo "=== Test 1: No @ prefix ===" && make -f /tmp/test_makefile test1 2>&1 || true
echo ""
echo "=== Test 2: @ on first line only ===" && make -f /tmp/test_makefile test2 2>&1 || true
echo ""
echo "=== Test 3: @ on all lines ===" && make -f /tmp/test_makefile test3 2>&1 || trueRepository: DefangLabs/defang
Length of output: 290
🌐 Web query:
GNU Make @ prefix recipe echo suppression
💡 Result:
In GNU Make, recipe lines are echoed (printed) before they run by default. If you prefix a recipe command with @, make will still execute the command but will not print that command line. (gnu.org)
Example:
all:
`@echo` "This message prints, but the command line is not echoed"
echo "This command line *is* echoed, then its output prints"Related options:
- Silence everything for a run:
make -s/make --silent(as if every recipe line began with@). (ftp.gnu.org) .ONESHELLcaveat: with.ONESHELL, GNU Make checks special prefixes like@only on the first recipe line (so put@on the first line if you want the whole recipe silent). (stackoverflow.com)
Citations:
- 1: https://www.gnu.org/software/make/manual/html_node/Echoing.html?utm_source=openai
- 2: https://ftp.gnu.org/old-gnu/Manuals/make-3.80/html_chapter/make_5.html?utm_source=openai
- 3: https://stackoverflow.com/questions/3477292/what-do-and-do-as-prefixes-to-recipe-lines-in-make?utm_source=openai
🏁 Script executed:
grep -n "ONESHELL\|SHELL" src/Makefile | head -20Repository: DefangLabs/defang
Length of output: 76
Add @ to suppress Make's recipe echo so these targets print clean output.
Without the @ prefix, GNU Make echoes each recipe line before executing it. This means the entire multi-line command will be printed to stdout before echo $$NEW_TAG runs, polluting the output for any consumer of this target's result.
🔧 Proposed fix
increment-patch-version:
- LATEST_TAG=$$(git tag -l 'v*' --sort=-v:refname | head -n1); \
+ `@LATEST_TAG`=$$(git tag -l 'v*' --sort=-v:refname | head -n1); \
NEW_TAG=$$(echo $$LATEST_TAG | awk -F. '{$$NF = $$NF + 1;} 1' OFS=.); \
echo $$NEW_TAG; \
git tag $$NEW_TAG
increment-minor-version:
- LATEST_TAG=$$(git tag -l 'v*' --sort=-v:refname | head -n1); \
+ `@LATEST_TAG`=$$(git tag -l 'v*' --sort=-v:refname | head -n1); \
NEW_TAG=$$(echo $$LATEST_TAG | awk -F. '{$$(NF-1) = $$(NF-1) + 1; $$NF = 0;} 1' OFS=.); \
echo $$NEW_TAG; \
git tag $$NEW_TAG
increment-major-version:
- LATEST_TAG=$$(git tag -l 'v*' --sort=-v:refname | head -n1); \
+ `@LATEST_TAG`=$$(git tag -l 'v*' --sort=-v:refname | head -n1); \
NEW_TAG=$$(echo $$LATEST_TAG | awk -F. '{maj=substr($$1,2)+1; printf "v%d.0.0\n", maj}'); \
echo $$NEW_TAG; \
git tag $$NEW_TAG🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/Makefile` around lines 129 - 146, The recipe lines that compute and print
NEW_TAG (the multi-line shell commands assigning LATEST_TAG/NEW_TAG and running
git tag) are being echoed by make; prefix each recipe line with @ to suppress
command echoing so the targets produce clean output—apply this to the unnamed
tag-increment target (the block that sets LATEST_TAG/NEW_TAG and runs git tag),
the increment-minor-version target, and the increment-major-version target,
keeping the existing tab indentation and preserving the exact shell commands
(LATEST_TAG=..., NEW_TAG=..., echo $$NEW_TAG; git tag $$NEW_TAG).
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/Makefile`:
- Around line 129-133: The Makefile recipe that computes LATEST_TAG and NEW_TAG
and runs "git tag $$NEW_TAG" can mask failures because the semicolon-separated
commands return the last command's exit code; update the increment targets to
enable immediate exit on failure by adding a "set -e" (or equivalent "set -o
errexit") at the start of the recipe so any failure (especially in "git tag
$$NEW_TAG") aborts the recipe and prevents writing an incorrect .new_tag; ensure
this change is applied to the targets that define LATEST_TAG and NEW_TAG and
perform "git tag" so errors are surfaced correctly.
| @LATEST_TAG=$$(git tag -l 'v*' --sort=-v:refname | head -n1); \ | ||
| NEW_TAG=$$(echo $$LATEST_TAG | awk -F. '{$$NF = $$NF + 1;} 1' OFS=.); \ | ||
| echo $$NEW_TAG; \ | ||
| git tag $$NEW_TAG; \ | ||
| echo $$NEW_TAG > .new_tag |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -u
cat >/tmp/makefile-release-check.mk <<'EOF'
bad:
`@NEW_TAG`=v1.2.3; \
echo $$NEW_TAG; \
false; \
echo $$NEW_TAG > /tmp/.new_tag.bad
good:
`@set` -e; \
NEW_TAG=v1.2.3; \
echo $$NEW_TAG; \
false; \
echo $$NEW_TAG > /tmp/.new_tag.good
EOF
rm -f /tmp/.new_tag.bad /tmp/.new_tag.good
echo "== bad target =="
if make -f /tmp/makefile-release-check.mk bad; then
echo "recipe reported success: yes"
else
echo "recipe reported success: no"
fi
test -f /tmp/.new_tag.bad && echo ".new_tag written after failure: yes" || echo ".new_tag written after failure: no"
echo
echo "== good target =="
if make -f /tmp/makefile-release-check.mk good; then
echo "recipe reported success: yes"
else
echo "recipe reported success: no"
fi
test -f /tmp/.new_tag.good && echo ".new_tag written after failure: yes" || echo ".new_tag written after failure: no"Repository: DefangLabs/defang
Length of output: 327
🏁 Script executed:
# First, let's look at the actual Makefile to see the current state
head -n 160 src/Makefile | tail -n 35Repository: DefangLabs/defang
Length of output: 1171
🏁 Script executed:
# Now let's test the bash behavior directly (without make) to verify the failure-masking claim
#!/bin/bash
set -u
echo "=== Testing bash failure-masking behavior ==="
echo
# Test 1: Multiple commands with last one succeeding (bash -c, no set -e)
echo "Test 1: Last command succeeds (no set -e) - recipe should report success"
bash -c 'false; echo "result" > /tmp/test1.txt' && echo "Exit status: success" || echo "Exit status: failure"
test -f /tmp/test1.txt && echo "File written: yes" || echo "File written: no"
echo
# Test 2: Same with set -e
echo "Test 2: Last command succeeds (with set -e) - recipe should still fail on false"
bash -c 'set -e; false; echo "result" > /tmp/test2.txt' && echo "Exit status: success" || echo "Exit status: failure"
test -f /tmp/test2.txt && echo "File written: yes" || echo "File written: no"
echo
# Test 3: Simulating the actual Makefile pattern without set -e
echo "Test 3: Makefile-like pattern - NEW_TAG fails, then echo succeeds"
bash -c 'NEW_TAG=$(false); echo "$NEW_TAG" > /tmp/test3.txt' 2>/dev/null && echo "Exit status: success" || echo "Exit status: failure"
test -f /tmp/test3.txt && echo "File written: yes" || echo "File written: no"
echo
# Test 4: Same pattern with set -e
echo "Test 4: Makefile-like pattern with set -e"
bash -c 'set -e; NEW_TAG=$(false); echo "$NEW_TAG" > /tmp/test4.txt' 2>/dev/null && echo "Exit status: success" || echo "Exit status: failure"
test -f /tmp/test4.txt && echo "File written: yes" || echo "File written: no"Repository: DefangLabs/defang
Length of output: 525
Add set -e to the increment targets to prevent masking git tag failures.
In semicolon-separated bash recipes, the exit status is determined by the last command. Here, if git tag $$NEW_TAG fails but echo $$NEW_TAG > .new_tag succeeds, the recipe exits with status 0. The release targets then proceed to push whatever tag is in .new_tag, potentially pushing an old or unintended version.
Suggested fix
increment-patch-version:
- `@LATEST_TAG`=$$(git tag -l 'v*' --sort=-v:refname | head -n1); \
+ `@set` -e; \
+ LATEST_TAG=$$(git tag -l 'v*' --sort=-v:refname | head -n1); \
NEW_TAG=$$(echo $$LATEST_TAG | awk -F. '{$$NF = $$NF + 1;} 1' OFS=.); \
echo $$NEW_TAG; \
git tag $$NEW_TAG; \
echo $$NEW_TAG > .new_tag
increment-minor-version:
- `@LATEST_TAG`=$$(git tag -l 'v*' --sort=-v:refname | head -n1); \
+ `@set` -e; \
+ LATEST_TAG=$$(git tag -l 'v*' --sort=-v:refname | head -n1); \
NEW_TAG=$$(echo $$LATEST_TAG | awk -F. '{$$(NF-1) = $$(NF-1) + 1; $$NF = 0;} 1' OFS=.); \
echo $$NEW_TAG; \
git tag $$NEW_TAG; \
echo $$NEW_TAG > .new_tag
increment-major-version:
- `@LATEST_TAG`=$$(git tag -l 'v*' --sort=-v:refname | head -n1); \
+ `@set` -e; \
+ LATEST_TAG=$$(git tag -l 'v*' --sort=-v:refname | head -n1); \
NEW_TAG=$$(echo $$LATEST_TAG | awk -F. '{maj=substr($$1,2)+1; printf "v%d.0.0\n", maj}'); \
echo $$NEW_TAG; \
git tag $$NEW_TAG; \
echo $$NEW_TAG > .new_tag🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/Makefile` around lines 129 - 133, The Makefile recipe that computes
LATEST_TAG and NEW_TAG and runs "git tag $$NEW_TAG" can mask failures because
the semicolon-separated commands return the last command's exit code; update the
increment targets to enable immediate exit on failure by adding a "set -e" (or
equivalent "set -o errexit") at the start of the recipe so any failure
(especially in "git tag $$NEW_TAG") aborts the recipe and prevents writing an
incorrect .new_tag; ensure this change is applied to the targets that define
LATEST_TAG and NEW_TAG and perform "git tag" so errors are surfaced correctly.
Description
Update the release tasks to print the tag when making it, and only push the release tags
Linked Issues
Checklist
Summary by CodeRabbit