Skip to content

Commit 43fb2fd

Browse files
authored
Improve failure diagnostics for release actions (#59)
When things go wrong, it'd be nice if the messages were clearer about what went wrong and how to remedy it. This pull request: - Adds richer environment dumps for JS and Python - Adds annotations and additional messages for failure conditions so that they appear prominently on the GHA workflow page (not just cryptic "process exited with code 1")
1 parent 1658a02 commit 43fb2fd

21 files changed

Lines changed: 203 additions & 85 deletions

actions/release/lang/js/publish/action.yml

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,11 @@ runs:
172172
- name: Install dependencies
173173
shell: bash
174174
working-directory: ${{ inputs.working_directory }}
175-
run: pnpm install --frozen-lockfile
175+
run: |
176+
pnpm install --frozen-lockfile || {
177+
echo "::error title=Dependency install failed::'pnpm install --frozen-lockfile' failed — the lockfile is likely out of sync with package.json (run 'pnpm install' and commit pnpm-lock.yaml)."
178+
exit 1
179+
}
176180
177181
- name: Build
178182
if: ${{ inputs.build == 'true' }}
@@ -208,10 +212,12 @@ runs:
208212
[ "$PROVENANCE" = "true" ] && ARGS+=(--provenance)
209213
if [ "$DRY_RUN" = "true" ]; then
210214
echo "DRY RUN: pnpm publish ${ARGS[*]} --dry-run"
211-
pnpm publish "${ARGS[@]}" --dry-run
212-
else
213-
pnpm publish "${ARGS[@]}"
215+
ARGS+=(--dry-run)
214216
fi
217+
pnpm publish "${ARGS[@]}" || {
218+
echo "::error title=npm publish failed::auth/ENEEDAUTH → the npm trusted publisher must list this repo, workflow, and (if gated) environment; 'cannot publish over <version>' → the version already exists on npm (bump it)."
219+
exit 1
220+
}
215221
216222
- name: Create GitHub release
217223
shell: bash
@@ -404,7 +410,7 @@ runs:
404410
if echo "$RESPONSE" | jq -e '.ok' > /dev/null 2>&1; then
405411
echo "Slack notification sent."
406412
elif [ "$FAIL_ON_ERROR" = "true" ]; then
407-
echo "Slack notification failed: $RESPONSE"
413+
echo "::error title=Slack notification failed::$RESPONSE"
408414
exit 1
409415
else
410416
echo "::warning::Slack notification failed: $RESPONSE"
@@ -420,10 +426,24 @@ runs:
420426
npm --version
421427
echo "=== pnpm ==="
422428
pnpm --version || echo "(unavailable)"
423-
echo "=== Registry ==="
424-
pnpm config get registry
425-
echo "=== package.json version ==="
426-
node -p "require('./package.json').version" 2>/dev/null || echo "(unavailable)"
429+
echo "=== package.json ==="
430+
node -e '
431+
const p = require("./package.json");
432+
console.log(JSON.stringify({
433+
name: p.name,
434+
version: p.version,
435+
private: p.private || false,
436+
packageManager: p.packageManager,
437+
engines: p.engines,
438+
devEngines: p.devEngines,
439+
publishConfig: p.publishConfig,
440+
lifecycle: Object.fromEntries(
441+
["prepublishOnly", "prepack", "prepare", "postpack"]
442+
.filter((k) => p.scripts && p.scripts[k])
443+
.map((k) => [k, p.scripts[k]]),
444+
),
445+
}, null, 2));
446+
' 2>/dev/null || echo "(package.json unavailable)"
427447
428448
- name: Build failure message
429449
if: ${{ failure() }}
@@ -479,7 +499,7 @@ runs:
479499
if echo "$RESPONSE" | jq -e '.ok' > /dev/null 2>&1; then
480500
echo "Slack notification sent."
481501
elif [ "$FAIL_ON_ERROR" = "true" ]; then
482-
echo "Slack notification failed: $RESPONSE"
502+
echo "::error title=Slack notification failed::$RESPONSE"
483503
exit 1
484504
else
485505
echo "::warning::Slack notification failed: $RESPONSE"

actions/release/lang/js/validate/action.yml

Lines changed: 35 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,11 @@ runs:
132132
- name: Install dependencies
133133
shell: bash
134134
working-directory: ${{ inputs.working_directory }}
135-
run: pnpm install --frozen-lockfile
135+
run: |
136+
pnpm install --frozen-lockfile || {
137+
echo "::error title=Dependency install failed::'pnpm install --frozen-lockfile' failed — the lockfile is likely out of sync with package.json (run 'pnpm install' and commit pnpm-lock.yaml)."
138+
exit 1
139+
}
136140
137141
- name: Read version
138142
id: read-version
@@ -142,11 +146,15 @@ runs:
142146
VERSION_OVERRIDE: ${{ inputs.version }}
143147
run: |
144148
if [ -n "$VERSION_OVERRIDE" ]; then
145-
echo "version=$VERSION_OVERRIDE" >> $GITHUB_OUTPUT
149+
VERSION="$VERSION_OVERRIDE"
146150
else
147151
VERSION=$(node -p "require('./package.json').version")
148-
echo "version=$VERSION" >> $GITHUB_OUTPUT
149152
fi
153+
if [ -z "$VERSION" ] || [ "$VERSION" = "undefined" ]; then
154+
echo "::error title=Could not read version::resolved an empty version — check the \"version\" field in package.json (or pass a version override)."
155+
exit 1
156+
fi
157+
echo "version=$VERSION" >> $GITHUB_OUTPUT
150158
151159
- name: Check npm version availability
152160
shell: bash
@@ -165,15 +173,14 @@ runs:
165173
"$REGISTRY/$PACKAGE_NAME/$VERSION")
166174
case "$CODE" in
167175
200)
168-
echo "Error: $PACKAGE_NAME@$VERSION already exists on npm — has the version been bumped?"
176+
echo "::error title=Version already published::$PACKAGE_NAME@$VERSION already exists on npm — has the version been bumped?"
169177
exit 1
170178
;;
171179
404)
172180
echo "$PACKAGE_NAME@$VERSION is available on npm."
173181
;;
174182
*)
175-
echo "Error: unexpected response from the npm registry (HTTP $CODE) checking $PACKAGE_NAME@$VERSION;"
176-
echo "cannot confirm the version is unpublished — aborting rather than risk a bad release."
183+
echo "::error title=Registry check failed::unexpected response from the npm registry (HTTP $CODE) checking $PACKAGE_NAME@$VERSION — cannot confirm the version is unpublished; aborting rather than risk a bad release."
177184
exit 1
178185
;;
179186
esac
@@ -188,7 +195,7 @@ runs:
188195
for c in "${OK[@]}"; do
189196
[ "$(echo "$c" | xargs)" = "$CHANNEL" ] && exit 0
190197
done
191-
echo "Error: release channel '$CHANNEL' is not in the allowed set: $ALLOWED"
198+
echo "::error title=Release channel not allowed::release channel '$CHANNEL' is not in the allowed set: $ALLOWED"
192199
exit 1
193200
194201
- name: Validate SHA format
@@ -197,7 +204,7 @@ runs:
197204
SHA: ${{ inputs.sha }}
198205
run: |
199206
if ! echo "$SHA" | grep -qE '^[0-9a-f]{40}$'; then
200-
echo "Error: sha must be a full 40-character commit SHA — branch names and short SHAs are not accepted."
207+
echo "::error title=Invalid release SHA::sha must be a full 40-character commit SHA — branch names and short SHAs are not accepted."
201208
exit 1
202209
fi
203210
@@ -217,9 +224,9 @@ runs:
217224
218225
if git rev-parse "$TAG" >/dev/null 2>&1; then
219226
if [ "$DRY_RUN" = "true" ]; then
220-
echo "Warning: Tag $TAG already exists — skipping in dry run"
227+
echo "::warning title=Release tag already exists::Tag $TAG already exists — skipping in dry run."
221228
else
222-
echo "Error: Tag $TAG already exists — has the version been bumped?"
229+
echo "::error title=Release tag already exists::Tag $TAG already exists — has the version been bumped?"
223230
exit 1
224231
fi
225232
fi
@@ -263,7 +270,21 @@ runs:
263270
npm --version
264271
echo "=== pnpm ==="
265272
pnpm --version || echo "(unavailable)"
266-
echo "=== Registry ==="
267-
pnpm config get registry
268-
echo "=== package.json version ==="
269-
node -p "require('./package.json').version" 2>/dev/null || echo "(unavailable)"
273+
echo "=== package.json ==="
274+
node -e '
275+
const p = require("./package.json");
276+
console.log(JSON.stringify({
277+
name: p.name,
278+
version: p.version,
279+
private: p.private || false,
280+
packageManager: p.packageManager,
281+
engines: p.engines,
282+
devEngines: p.devEngines,
283+
publishConfig: p.publishConfig,
284+
lifecycle: Object.fromEntries(
285+
["prepublishOnly", "prepack", "prepare", "postpack"]
286+
.filter((k) => p.scripts && p.scripts[k])
287+
.map((k) => [k, p.scripts[k]]),
288+
),
289+
}, null, 2));
290+
' 2>/dev/null || echo "(package.json unavailable)"

actions/release/lang/py/publish/action.yml

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,10 @@ runs:
179179
else
180180
ARGS+=(--no-attestations)
181181
fi
182-
uv publish "${ARGS[@]}"
182+
uv publish "${ARGS[@]}" || {
183+
echo "::error title=PyPI publish failed::auth/OIDC error → the PyPI trusted publisher must list this repo, workflow, and (if gated) environment; a 400 'File already exists' → the version is already on PyPI (bump it)."
184+
exit 1
185+
}
183186
184187
- name: Create GitHub release
185188
shell: bash
@@ -372,7 +375,7 @@ runs:
372375
if echo "$RESPONSE" | jq -e '.ok' > /dev/null 2>&1; then
373376
echo "Slack notification sent."
374377
elif [ "$FAIL_ON_ERROR" = "true" ]; then
375-
echo "Slack notification failed: $RESPONSE"
378+
echo "::error title=Slack notification failed::$RESPONSE"
376379
exit 1
377380
else
378381
echo "::warning::Slack notification failed: $RESPONSE"
@@ -390,8 +393,20 @@ runs:
390393
echo "${UV_PYTHON:-(unset)}"
391394
echo "=== Built artifacts ==="
392395
ls -la dist 2>/dev/null || echo "(no dist/)"
393-
echo "=== pyproject.toml ==="
394-
head -40 pyproject.toml 2>/dev/null || echo "(no pyproject.toml)"
396+
echo "=== pyproject.toml (release-relevant) ==="
397+
uv run --no-project python -c '
398+
import json, tomllib, pathlib
399+
t = tomllib.loads(pathlib.Path("pyproject.toml").read_text())
400+
proj = t.get("project", {})
401+
print(json.dumps({
402+
"name": proj.get("name"),
403+
"version": proj.get("version") or ("(dynamic)" if "version" in proj.get("dynamic", []) else None),
404+
"requires-python": proj.get("requires-python"),
405+
"dependencies": proj.get("dependencies"),
406+
"optional-dependencies": sorted((proj.get("optional-dependencies") or {}).keys()),
407+
"build-system": t.get("build-system"),
408+
}, indent=2))
409+
' 2>/dev/null || echo "(pyproject.toml unavailable)"
395410
396411
- name: Build failure message
397412
if: ${{ failure() }}
@@ -447,7 +462,7 @@ runs:
447462
if echo "$RESPONSE" | jq -e '.ok' > /dev/null 2>&1; then
448463
echo "Slack notification sent."
449464
elif [ "$FAIL_ON_ERROR" = "true" ]; then
450-
echo "Slack notification failed: $RESPONSE"
465+
echo "::error title=Slack notification failed::$RESPONSE"
451466
exit 1
452467
else
453468
echo "::warning::Slack notification failed: $RESPONSE"

actions/release/lang/py/validate/action.yml

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -112,13 +112,17 @@ runs:
112112
VERSION_FILE: ${{ inputs.version_file }}
113113
run: |
114114
if [ -n "$VERSION_OVERRIDE" ]; then
115-
echo "version=$VERSION_OVERRIDE" >> "$GITHUB_OUTPUT"
115+
VERSION="$VERSION_OVERRIDE"
116116
else
117117
# exec the version file in an isolated namespace (no package import needed),
118118
# matching how the SDKs expose VERSION in a standalone version.py.
119119
VERSION=$(uv run --no-project python -c "import runpy,sys; print(runpy.run_path(sys.argv[1])['VERSION'])" "$VERSION_FILE")
120-
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
121120
fi
121+
if [ -z "$VERSION" ] || [ "$VERSION" = "None" ]; then
122+
echo "::error title=Could not read version::resolved an empty version — check VERSION in $VERSION_FILE (or pass a version override)."
123+
exit 1
124+
fi
125+
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
122126
123127
- name: Check PyPI version availability
124128
shell: bash
@@ -135,15 +139,14 @@ runs:
135139
"https://pypi.org/pypi/$PACKAGE_NAME/$VERSION/json")
136140
case "$CODE" in
137141
200)
138-
echo "Error: $PACKAGE_NAME $VERSION already exists on PyPI — has the version been bumped?"
142+
echo "::error title=Version already published::$PACKAGE_NAME $VERSION already exists on PyPI — has the version been bumped?"
139143
exit 1
140144
;;
141145
404)
142146
echo "$PACKAGE_NAME $VERSION is available on PyPI."
143147
;;
144148
*)
145-
echo "Error: unexpected response from PyPI (HTTP $CODE) checking $PACKAGE_NAME $VERSION;"
146-
echo "cannot confirm the version is unpublished — aborting rather than risk a bad release."
149+
echo "::error title=Registry check failed::unexpected response from PyPI (HTTP $CODE) checking $PACKAGE_NAME $VERSION — cannot confirm the version is unpublished; aborting rather than risk a bad release."
147150
exit 1
148151
;;
149152
esac
@@ -154,7 +157,7 @@ runs:
154157
SHA: ${{ inputs.sha }}
155158
run: |
156159
if ! echo "$SHA" | grep -qE '^[0-9a-f]{40}$'; then
157-
echo "Error: sha must be a full 40-character commit SHA — branch names and short SHAs are not accepted."
160+
echo "::error title=Invalid release SHA::sha must be a full 40-character commit SHA — branch names and short SHAs are not accepted."
158161
exit 1
159162
fi
160163
@@ -174,9 +177,9 @@ runs:
174177
175178
if git rev-parse "$TAG" >/dev/null 2>&1; then
176179
if [ "$DRY_RUN" = "true" ]; then
177-
echo "Warning: Tag $TAG already exists — skipping in dry run"
180+
echo "::warning title=Release tag already exists::Tag $TAG already exists — skipping in dry run."
178181
else
179-
echo "Error: Tag $TAG already exists — has the version been bumped?"
182+
echo "::error title=Release tag already exists::Tag $TAG already exists — has the version been bumped?"
180183
exit 1
181184
fi
182185
fi
@@ -228,5 +231,17 @@ runs:
228231
echo "${UV_PYTHON:-(unset)}"
229232
echo "=== Built artifacts ==="
230233
ls -la dist 2>/dev/null || echo "(no dist/)"
231-
echo "=== pyproject.toml ==="
232-
head -40 pyproject.toml 2>/dev/null || echo "(no pyproject.toml)"
234+
echo "=== pyproject.toml (release-relevant) ==="
235+
uv run --no-project python -c '
236+
import json, tomllib, pathlib
237+
t = tomllib.loads(pathlib.Path("pyproject.toml").read_text())
238+
proj = t.get("project", {})
239+
print(json.dumps({
240+
"name": proj.get("name"),
241+
"version": proj.get("version") or ("(dynamic)" if "version" in proj.get("dynamic", []) else None),
242+
"requires-python": proj.get("requires-python"),
243+
"dependencies": proj.get("dependencies"),
244+
"optional-dependencies": sorted((proj.get("optional-dependencies") or {}).keys()),
245+
"build-system": t.get("build-system"),
246+
}, indent=2))
247+
' 2>/dev/null || echo "(pyproject.toml unavailable)"

actions/release/lang/ruby/publish/action.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -305,7 +305,7 @@ runs:
305305
if echo "$RESPONSE" | jq -e '.ok' > /dev/null 2>&1; then
306306
echo "Slack notification sent."
307307
elif [ "$FAIL_ON_ERROR" = "true" ]; then
308-
echo "Slack notification failed: $RESPONSE"
308+
echo "::error title=Slack notification failed::$RESPONSE"
309309
exit 1
310310
else
311311
echo "::warning::Slack notification failed: $RESPONSE"
@@ -378,7 +378,7 @@ runs:
378378
if echo "$RESPONSE" | jq -e '.ok' > /dev/null 2>&1; then
379379
echo "Slack notification sent."
380380
elif [ "$FAIL_ON_ERROR" = "true" ]; then
381-
echo "Slack notification failed: $RESPONSE"
381+
echo "::error title=Slack notification failed::$RESPONSE"
382382
exit 1
383383
else
384384
echo "::warning::Slack notification failed: $RESPONSE"

actions/release/lang/ruby/validate/action.yml

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -84,11 +84,15 @@ runs:
8484
VERSION_MODULE: ${{ inputs.version_module }}
8585
run: |
8686
if [ -n "$VERSION_OVERRIDE" ]; then
87-
echo "version=$VERSION_OVERRIDE" >> $GITHUB_OUTPUT
87+
VERSION="$VERSION_OVERRIDE"
8888
else
8989
VERSION=$(ruby -r "./$VERSION_FILE" -e "puts Object.const_get(ENV.fetch('VERSION_MODULE')).const_get(:VERSION)")
90-
echo "version=$VERSION" >> $GITHUB_OUTPUT
9190
fi
91+
if [ -z "$VERSION" ]; then
92+
echo "::error title=Could not read version::resolved an empty version — check VERSION in $VERSION_FILE (module $VERSION_MODULE), or pass a version override."
93+
exit 1
94+
fi
95+
echo "version=$VERSION" >> $GITHUB_OUTPUT
9296
9397
- name: Check RubyGems version availability
9498
shell: bash
@@ -105,15 +109,14 @@ runs:
105109
"https://rubygems.org/api/v2/rubygems/$PACKAGE_NAME/versions/$VERSION.json")
106110
case "$CODE" in
107111
200)
108-
echo "Error: $PACKAGE_NAME $VERSION already exists on RubyGems — has the version been bumped?"
112+
echo "::error title=Version already published::$PACKAGE_NAME $VERSION already exists on RubyGems — has the version been bumped?"
109113
exit 1
110114
;;
111115
404)
112116
echo "$PACKAGE_NAME $VERSION is available on RubyGems."
113117
;;
114118
*)
115-
echo "Error: unexpected response from RubyGems (HTTP $CODE) checking $PACKAGE_NAME $VERSION;"
116-
echo "cannot confirm the version is unpublished — aborting rather than risk a bad release."
119+
echo "::error title=Registry check failed::unexpected response from RubyGems (HTTP $CODE) checking $PACKAGE_NAME $VERSION — cannot confirm the version is unpublished; aborting rather than risk a bad release."
117120
exit 1
118121
;;
119122
esac
@@ -124,7 +127,7 @@ runs:
124127
SHA: ${{ inputs.sha }}
125128
run: |
126129
if ! echo "$SHA" | grep -qE '^[0-9a-f]{40}$'; then
127-
echo "Error: sha must be a full 40-character commit SHA — branch names and short SHAs are not accepted."
130+
echo "::error title=Invalid release SHA::sha must be a full 40-character commit SHA — branch names and short SHAs are not accepted."
128131
exit 1
129132
fi
130133
@@ -144,9 +147,9 @@ runs:
144147
145148
if git rev-parse "$TAG" >/dev/null 2>&1; then
146149
if [ "$DRY_RUN" = "true" ]; then
147-
echo "Warning: Tag $TAG already exists — skipping in dry run"
150+
echo "::warning title=Release tag already exists::Tag $TAG already exists — skipping in dry run."
148151
else
149-
echo "Error: Tag $TAG already exists — has the version been bumped?"
152+
echo "::error title=Release tag already exists::Tag $TAG already exists — has the version been bumped?"
150153
exit 1
151154
fi
152155
fi

actions/release/notify-pending/action.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,7 @@ runs:
202202
if echo "$RESPONSE" | jq -e '.ok' > /dev/null 2>&1; then
203203
echo "Slack notification sent."
204204
elif [ "$FAIL_ON_ERROR" = "true" ]; then
205-
echo "Slack notification failed: $RESPONSE"
205+
echo "::error title=Slack notification failed::$RESPONSE"
206206
exit 1
207207
else
208208
echo "::warning::Slack notification failed: $RESPONSE"

0 commit comments

Comments
 (0)