From f56d67a582812772f24f50aaf1d5eb43159f2a6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobiasz=20K=C4=99dzierski?= Date: Sat, 22 Aug 2026 19:34:49 +0200 Subject: [PATCH 1/3] Compare boolean inputs instead of executing them `if ${INPUT_FORCE}; then` did not compare the input, it ran it as a command. `force: true` worked only because /usr/bin/true exists, while `force: yes` ran `yes` and hung the job until the runner timed out, and any other string was executed verbatim. Both flags are now compared against the string "true". Values that are not "true" are inert, which matches how the README documents them. test_start.sh covers this with a stub `git` on PATH, so it needs no token and no network. It hangs on the old code and passes on the new. Also adds a .gitignore for .DS_Store/.idea and a read-only default token scope for the CI workflow. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01X1sz7entAqb4B2Hycm9YF4 --- .github/workflows/ci.yaml | 5 ++++ .gitignore | 2 ++ start.sh | 4 +-- test_start.sh | 62 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 71 insertions(+), 2 deletions(-) create mode 100644 .gitignore create mode 100755 test_start.sh diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 76eef83..3d89447 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -6,6 +6,9 @@ on: pull_request: branches: [master] +permissions: + contents: read + jobs: statics: name: Static checks @@ -23,6 +26,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v7 + - name: start.sh flag handling + run: ./test_start.sh - name: Run without upstream_repository (expected to fail) id: run continue-on-error: true diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..af56f61 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.DS_Store +.idea/ diff --git a/start.sh b/start.sh index bbf37f9..5084076 100755 --- a/start.sh +++ b/start.sh @@ -22,11 +22,11 @@ echo "Synchronizing repository ${TARGET_REPOSITORY}:${INPUT_TARGET_BRANCH} with exit 1; }; -if ${INPUT_FORCE}; then +if [ "${INPUT_FORCE}" = "true" ]; then _FORCE_OPTION='--force' fi -if ${INPUT_TAGS}; then +if [ "${INPUT_TAGS}" = "true" ]; then _TAGS='--follow-tags --tags' fi diff --git a/test_start.sh b/test_start.sh new file mode 100755 index 0000000..d99441d --- /dev/null +++ b/test_start.sh @@ -0,0 +1,62 @@ +#!/bin/sh +# Asserts start.sh turns the force/tags inputs into the right git push flags. +# Uses a stub `git` on PATH, so no token and no network are needed. +set -e + +here=$(cd "$(dirname "$0")" && pwd) +tmp=$(mktemp -d) +trap 'rm -rf "$tmp"' EXIT +mkdir "$tmp/bin" + +cat > "$tmp/bin/git" <<'STUB' +#!/bin/sh +echo "git $*" >> "$GIT_LOG" +# `clone` must leave behind the directory start.sh cds into +[ "$1" = "clone" ] && mkdir -p "$(basename "$2" .git)" +exit 0 +STUB +chmod +x "$tmp/bin/git" + +# Prints the push command start.sh would run for the given env. +push_cmd() { + GIT_LOG="$tmp/log"; export GIT_LOG; : > "$GIT_LOG" + ( + cd "$tmp" + PATH="$tmp/bin:$PATH" \ + INPUT_GITHUB_TOKEN=dummy \ + INPUT_UPSTREAM_REPOSITORY=owner/repo \ + env "$@" sh "$here/start.sh" > /dev/null + ) + grep '^git push' "$GIT_LOG" +} + +assert_has() { + case "$1" in + *"$2"*) ;; + *) echo "FAIL: expected '$2' in: $1" >&2; exit 1 ;; + esac +} + +assert_lacks() { + case "$1" in + *"$2"*) echo "FAIL: unexpected '$2' in: $1" >&2; exit 1 ;; + esac +} + +out=$(push_cmd) +assert_lacks "$out" "--force" +assert_lacks "$out" "--tags" + +out=$(push_cmd INPUT_FORCE=true INPUT_TAGS=true) +assert_has "$out" "--force" +assert_has "$out" "--follow-tags --tags" + +# A non-"true" value must be inert. These used to be *executed* as commands, +# so INPUT_FORCE=yes ran `yes` and hung the job forever. +for value in yes 1 True false ''; do + out=$(push_cmd "INPUT_FORCE=$value" "INPUT_TAGS=$value") + assert_lacks "$out" "--force" + assert_lacks "$out" "--tags" +done + +echo "start.sh flag handling: OK" From 4657ac2c5ce0690fd27caf9d60fc3b615bea27cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobiasz=20K=C4=99dzierski?= Date: Sat, 22 Aug 2026 19:41:14 +0200 Subject: [PATCH 2/3] Replace test_start.sh with a pygrep lint hook test_start.sh was 55 lines of stub-git plumbing guarding two lines of string comparison. It paid for itself while proving the bug and the fix, but as a permanent fixture the ratio does not hold up. The regression worth guarding is a syntax pattern, so a pygrep hook catches it directly: it fails on `if ${INPUT_...}` and reports the offending line numbers. Verified against the pre-fix start.sh. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01X1sz7entAqb4B2Hycm9YF4 --- .github/workflows/ci.yaml | 2 -- .pre-commit-config.yaml | 7 +++++ test_start.sh | 62 --------------------------------------- 3 files changed, 7 insertions(+), 64 deletions(-) delete mode 100755 test_start.sh diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 3d89447..2051807 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -26,8 +26,6 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v7 - - name: start.sh flag handling - run: ./test_start.sh - name: Run without upstream_repository (expected to fail) id: run continue-on-error: true diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 81ba5fc..4026888 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -20,3 +20,10 @@ repos: - "4" - "--title" - "**Table of Contents**" +- repo: local + hooks: + - id: no-input-eval + name: Inputs must be compared, not executed + language: pygrep + entry: 'if \$\{?INPUT_' + files: ^start\.sh$ diff --git a/test_start.sh b/test_start.sh deleted file mode 100755 index d99441d..0000000 --- a/test_start.sh +++ /dev/null @@ -1,62 +0,0 @@ -#!/bin/sh -# Asserts start.sh turns the force/tags inputs into the right git push flags. -# Uses a stub `git` on PATH, so no token and no network are needed. -set -e - -here=$(cd "$(dirname "$0")" && pwd) -tmp=$(mktemp -d) -trap 'rm -rf "$tmp"' EXIT -mkdir "$tmp/bin" - -cat > "$tmp/bin/git" <<'STUB' -#!/bin/sh -echo "git $*" >> "$GIT_LOG" -# `clone` must leave behind the directory start.sh cds into -[ "$1" = "clone" ] && mkdir -p "$(basename "$2" .git)" -exit 0 -STUB -chmod +x "$tmp/bin/git" - -# Prints the push command start.sh would run for the given env. -push_cmd() { - GIT_LOG="$tmp/log"; export GIT_LOG; : > "$GIT_LOG" - ( - cd "$tmp" - PATH="$tmp/bin:$PATH" \ - INPUT_GITHUB_TOKEN=dummy \ - INPUT_UPSTREAM_REPOSITORY=owner/repo \ - env "$@" sh "$here/start.sh" > /dev/null - ) - grep '^git push' "$GIT_LOG" -} - -assert_has() { - case "$1" in - *"$2"*) ;; - *) echo "FAIL: expected '$2' in: $1" >&2; exit 1 ;; - esac -} - -assert_lacks() { - case "$1" in - *"$2"*) echo "FAIL: unexpected '$2' in: $1" >&2; exit 1 ;; - esac -} - -out=$(push_cmd) -assert_lacks "$out" "--force" -assert_lacks "$out" "--tags" - -out=$(push_cmd INPUT_FORCE=true INPUT_TAGS=true) -assert_has "$out" "--force" -assert_has "$out" "--follow-tags --tags" - -# A non-"true" value must be inert. These used to be *executed* as commands, -# so INPUT_FORCE=yes ran `yes` and hung the job forever. -for value in yes 1 True false ''; do - out=$(push_cmd "INPUT_FORCE=$value" "INPUT_TAGS=$value") - assert_lacks "$out" "--force" - assert_lacks "$out" "--tags" -done - -echo "start.sh flag handling: OK" From a17dcce6edd4cf45d672d2d3389076242c8c7743 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobiasz=20K=C4=99dzierski?= Date: Sat, 22 Aug 2026 20:16:01 +0200 Subject: [PATCH 3/3] Close the quoted/loop gap in the no-input-eval hook The pattern missed `if "${INPUT_X}"`, which still executes the input, and the while/until forms. Widening it costs ten characters and no readability, and it does not fire on the `[ "$x" = "true" ]` or `[ -z "$x" ]` forms the script actually uses. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01X1sz7entAqb4B2Hycm9YF4 --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 4026888..c148cf5 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -25,5 +25,5 @@ repos: - id: no-input-eval name: Inputs must be compared, not executed language: pygrep - entry: 'if \$\{?INPUT_' + entry: '(if|elif|while|until) +"?\$\{?INPUT_' files: ^start\.sh$