Skip to content

feat(shell-ci): fail when a script invoked by path is not executable in git - #144

Merged
Jaro-c merged 2 commits into
mainfrom
ci/shell-ci-exec-bit
Aug 24, 2026
Merged

Jaro-c merged 2 commits into
mainfrom
ci/shell-ci-exec-bit

Conversation

@Jaro-c

@Jaro-c Jaro-c commented Aug 24, 2026

Copy link
Copy Markdown
Member

chmod +x in a working tree is local state. The mode git records is what reaches the runner, and when they disagree the job dies with exit 126 and Permission denied — a message about the file, not about the mode, which is why it costs a round trip to diagnose every time.

It happened twice in one day in apt: scripts/build-installers.sh in #116 this morning, tests/build-installers.test.sh a few hours later.

Deliberately narrow

It does not assert that every .sh is 0755. That would be wrong: apt/scripts/install-template.sh is read and substituted, never executed, and is correctly 0644.

The list is derived from how the calling repos workflows actually invoke each script — every ./something.shmentioned under.github/workflows/. A repo that runs bash path` finds nothing and passes. Measured across the org:

invokes ./path invokes bash path
apt (11), homebrew-tap (3), scoop-bucket (3) podup (3), epistle (1), helmly (1), unitpm (2)

Those four on the right hold 21 non-executable scripts between them and not one is a defect. A blanket "every .sh must be 0755" gate would have turned all 21 red for nothing. This finds zero of them.

Default on, not opt-in

The SHA pin is the buffer. No consumer sees this until it deliberately bumps its pin — which is exactly the moment to look at a new red. A guard nobody enables protects nobody.

Run, not read

I executed the logic against three real repository states rather than reviewing it:

  • apt today → Every script invoked by path is executable in git. (rc=0)
  • apt at commit ee6de9d, the broken state from this afternoon → rc=1, tests/build-installers.test.sh is invoked as ./tests/... but git records mode 100644. It catches the actual failure.
  • podup → No script is invoked as ./path by any workflow; nothing to check. (rc=0)

actionlint and a YAML parse are clean.

Before tagging

Per standards/ci, a reusable is proved on a real consumer before a tag is cut. Once this merges I will pin apt at the merge SHA, wait for its CI, and only then propose the release.

Jaro-c added 2 commits August 23, 2026 21:43
…in git

chmod +x in a working tree is local state; the mode git records is what
reaches the runner. When they disagree the job dies with exit 126 and
"Permission denied" - a message about the file, not about the mode,
which is why it costs a round trip to diagnose every time.

It happened twice in one day in apt: once on scripts/build-installers.sh
(#116) and once on tests/build-installers.test.sh a few hours later.

The check is deliberately narrow. It does not assert that every .sh is
0755, which would be wrong - apt/scripts/install-template.sh is read and
substituted, never executed, and is correctly 0644. It derives the list
from how the calling repo workflows actually invoke each script, so a
repo using `bash path` finds nothing. Measured: apt, homebrew-tap and
scoop-bucket invoke by path; podup, epistle, helmly, unitpm and
glyndor.net do not, and have 21 non-executable scripts between them that
are not defects.

Default on rather than opt-in, because the SHA pin is the buffer: no
consumer sees it until it bumps its pin deliberately, which is the
moment to look at a new red.

I ran the logic against three real states rather than reading it: apt
today passes, apt at the broken commit fails naming the file and the
mode, podup reports nothing to check.

Signed-off-by: Jaro-c <75870284+Jaro-c@users.noreply.github.com>
docs-current caught it: the pages are generated by
scripts/render-reusable-docs.py and the check fails when the committed
ones disagree with the workflows. Adding check-exec-bit made shell-ci.md
stale, which is the gate working as designed.

Signed-off-by: Jaro-c <75870284+Jaro-c@users.noreply.github.com>
@Jaro-c
Jaro-c merged commit 8ae47cc into main Aug 24, 2026
9 checks passed
@Jaro-c
Jaro-c deleted the ci/shell-ci-exec-bit branch August 24, 2026 03:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant