Skip to content

fix(actions): invoke publish.sh via bash, not direct exec#28

Merged
jan-kubica merged 2 commits into
mainfrom
fix/explicit-bash-invoke
May 13, 2026
Merged

fix(actions): invoke publish.sh via bash, not direct exec#28
jan-kubica merged 2 commits into
mainfrom
fix/explicit-bash-invoke

Conversation

@jan-kubica
Copy link
Copy Markdown
Contributor

@jan-kubica jan-kubica commented May 13, 2026

Symptom

`stella/stdnum#95`'s release dispatch against `v0.0.1` exited 1 with no output and a script runtime of ~200ms. The composite step's env block was emitted to the log, then immediately `##[error]Process completed with exit code 1`. No `::error::` annotation, no `npm view` call, no `set -x` trace, nothing.

That pattern fits "bash failed to exec the script" — `set -e` exits on a failed exec without producing the script's own output.

Root cause

`publish.sh` is committed with git tree mode `100755` (verified). But the runner-side checkout of a composite action does not reliably preserve the executable bit. The action ADR documents this explicitly:

"you'll have to use `chmod` before running each script if you do not git check in your script files into your github repo with the executable bit turned on."
actions/runner ADR 0549-composite-run-steps

And the community has hit it repeatedly even when the mode IS committed: community/discussions/26239.

Fix

Two documented patterns are correct:

  1. `chmod +x` step + direct exec. Official tutorial pattern. Two steps; still mode-dependent at exec time (relies on the chmod actually sticking).
  2. `bash `. Bash reads the file regardless of mode. One step; eliminates the failure mode entirely instead of relying on mode preservation.

This PR uses option 2.

```yaml
run: bash "${{ github.action_path }}/publish.sh"
```

Test plan

  • research: official docs + runner ADR + community discussions all confirm the failure mode and both fixes
  • shellcheck clean
  • post-merge: bump SHA pin in stella/stdnum's release.yml, re-dispatch against `v0.0.1`. The composite should reach the `already_published` early-return and exit 0 without attempting to re-publish.

References

The previous shape ran the script path as a command. That requires
the file's exec bit to be set on the runner's action checkout —
which is mostly true but not guaranteed: the runner cloning behaviour
can drop the file mode in some configurations, and "permission
denied" then surfaces as exit 1 with **no script output**, which is
exactly the failure mode the stdnum v0.0.1 dispatch hit.

Switching to `bash <path>` removes the dependency: bash reads and
interprets the file regardless of its mode.

Surfaced by stella/stdnum's release dispatch against v0.0.1 — the
script's `chmod +x` was preserved in git (tree mode 100755 confirmed)
but the runner-side artifact did not honour it.
@jan-kubica jan-kubica requested a review from nnad3N as a code owner May 13, 2026 15:03
Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request updates the npm-publish-hardened action to invoke the publish.sh script explicitly using bash, which prevents potential execution permission issues in the GitHub Actions runner. A review comment suggests adding the -e flag to the bash command to ensure the script fails immediately on errors, as the shebang line is ignored during explicit invocation.

# fail with "permission denied" while still surfacing as exit 1
# with no script output. `bash <path>` removes that dependency
# entirely — bash reads the file regardless of its mode.
run: bash "${{ github.action_path }}/publish.sh"
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

When a script is invoked explicitly via bash <path>, the shebang line (e.g., #!/bin/bash -e) is ignored by the shell. To ensure the action fails immediately if any command within publish.sh fails, it is recommended to explicitly pass the -e flag to the bash command. This is especially important for a "hardened" action to prevent silent failures during the publish process.

      run: bash -e "${{ github.action_path }}/publish.sh"

@jan-kubica jan-kubica marked this pull request as draft May 13, 2026 15:08
@jan-kubica jan-kubica marked this pull request as ready for review May 13, 2026 15:11
@jan-kubica
Copy link
Copy Markdown
Contributor Author

CC on behalf of @jan-kubica

Applied in latest commit. Worth noting that publish.sh's second line is `set -euo pipefail`, so the safety settings are already in effect from the script's perspective — only line 1 (the shebang comment) runs without them, and that's a no-op. But passing the flags to the sub-bash is correct defense-in-depth and aligns with the GitHub Actions runner's own default shell config (`bash --noprofile --norc -e -o pipefail {0}`).

The shebang's flags are ignored when invoked via `bash <path>`.
publish.sh's second line is `set -euo pipefail`, so the safety
settings are in effect from the script's perspective, but making the
sub-bash flags explicit aligns with the runner's own default shell
config (`bash --noprofile --norc -e -o pipefail {0}`) and removes
the empty-but-uncovered window before line 2 executes.

Addresses gemini medium on PR #28.
@jan-kubica jan-kubica merged commit 9845e4f into main May 13, 2026
1 check passed
@jan-kubica jan-kubica deleted the fix/explicit-bash-invoke branch May 13, 2026 15:16
@github-actions github-actions Bot locked and limited conversation to collaborators May 13, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant