From b1556a6e1320498599ecebced57df5dbd902ab55 Mon Sep 17 00:00:00 2001 From: injectedfusion <6646111+injectedfusion@users.noreply.github.com> Date: Wed, 18 Feb 2026 12:08:12 +0100 Subject: [PATCH 1/3] feat: add require-signed-commits hook Checks commit.gpgsign=true and user.signingkey is set before allowing a commit. Blocks unsigned commits in non-interactive shells (e.g. agentic AI workflows) where signing can silently fall through. Usage in .pre-commit-config.yaml: - repo: https://github.com/injectedfusion/pre-commit-hooks rev: hooks: - id: require-signed-commits --- .pre-commit-hooks.yaml | 13 +++++++++++ hooks/require-signed-commits.sh | 41 +++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+) create mode 100755 hooks/require-signed-commits.sh diff --git a/.pre-commit-hooks.yaml b/.pre-commit-hooks.yaml index d6ccb20..2111a56 100644 --- a/.pre-commit-hooks.yaml +++ b/.pre-commit-hooks.yaml @@ -31,3 +31,16 @@ language: script types: [yaml] stages: [pre-commit] + +- id: require-signed-commits + name: require signed commits + description: > + Fails if commit.gpgsign is not true or user.signingkey is not set. + Enforces GPG/SSH commit signing discipline, especially important in + agentic AI workflows where unsigned commits can slip through + non-interactive shells. + entry: hooks/require-signed-commits.sh + language: script + always_run: true + pass_filenames: false + stages: [pre-commit] diff --git a/hooks/require-signed-commits.sh b/hooks/require-signed-commits.sh new file mode 100755 index 0000000..782a0d8 --- /dev/null +++ b/hooks/require-signed-commits.sh @@ -0,0 +1,41 @@ +#!/usr/bin/env bash +# require-signed-commits — fail if the commit being created will be unsigned. +# Prevents unsigned commits from entering the repo, enforcing GPG/SSH signing +# discipline especially important in agentic AI workflows. +# +# Checks git config commit.gpgsign is true, and that a signing key is configured. +# Gracefully skips if gpgsign is explicitly disabled (opt-out pattern). + +set -euo pipefail + +# Check if signing is configured +gpgsign="$(git config --get commit.gpgsign 2>/dev/null || echo 'false')" + +if [[ "$gpgsign" != "true" ]]; then + echo "✗ Unsigned commit blocked: commit.gpgsign is not set to true" + echo "" + echo "To enable commit signing:" + echo " git config --global commit.gpgsign true" + echo " git config --global user.signingkey " + echo "" + echo "If using 1Password SSH agent:" + echo " git config --global gpg.format ssh" + echo " git config --global user.signingkey " + echo "" + echo "To bypass (not recommended): git commit --no-verify" + exit 1 +fi + +# Check a signing key is set +signingkey="$(git config --get user.signingkey 2>/dev/null || echo '')" +gpg_format="$(git config --get gpg.format 2>/dev/null || echo 'openpgp')" + +if [[ -z "$signingkey" ]]; then + echo "✗ Unsigned commit blocked: commit.gpgsign=true but user.signingkey is not set" + echo "" + echo "Set your signing key:" + echo " git config --global user.signingkey " + exit 1 +fi + +exit 0 From 178b231bff6ae45c9548e414a3eb7dc6c090d76d Mon Sep 17 00:00:00 2001 From: injectedfusion <6646111+injectedfusion@users.noreply.github.com> Date: Wed, 18 Feb 2026 12:10:22 +0100 Subject: [PATCH 2/3] fix: correct misleading comment and remove unused gpg_format variable --- hooks/require-signed-commits.sh | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/hooks/require-signed-commits.sh b/hooks/require-signed-commits.sh index 782a0d8..f1eb1ff 100755 --- a/hooks/require-signed-commits.sh +++ b/hooks/require-signed-commits.sh @@ -3,8 +3,7 @@ # Prevents unsigned commits from entering the repo, enforcing GPG/SSH signing # discipline especially important in agentic AI workflows. # -# Checks git config commit.gpgsign is true, and that a signing key is configured. -# Gracefully skips if gpgsign is explicitly disabled (opt-out pattern). +# Fails unless commit.gpgsign is explicitly set to true and user.signingkey is configured. set -euo pipefail @@ -28,7 +27,6 @@ fi # Check a signing key is set signingkey="$(git config --get user.signingkey 2>/dev/null || echo '')" -gpg_format="$(git config --get gpg.format 2>/dev/null || echo 'openpgp')" if [[ -z "$signingkey" ]]; then echo "✗ Unsigned commit blocked: commit.gpgsign=true but user.signingkey is not set" From c36ba68e92d8f9628b2fcae564e82aded132cfb2 Mon Sep 17 00:00:00 2001 From: injectedfusion <6646111+injectedfusion@users.noreply.github.com> Date: Wed, 18 Feb 2026 12:12:32 +0100 Subject: [PATCH 3/3] ci: add PR pipeline with shellcheck and Claude reviewer --- .github/workflows/pr-pipeline.yml | 110 ++++++++++++++++++++++++++++++ 1 file changed, 110 insertions(+) create mode 100644 .github/workflows/pr-pipeline.yml diff --git a/.github/workflows/pr-pipeline.yml b/.github/workflows/pr-pipeline.yml new file mode 100644 index 0000000..6630262 --- /dev/null +++ b/.github/workflows/pr-pipeline.yml @@ -0,0 +1,110 @@ +name: PR Pipeline + +on: + pull_request: + types: [opened, synchronize, reopened] + issue_comment: + types: [created] + pull_request_review_comment: + types: [created] + +jobs: + shellcheck: + name: ShellCheck + # Only run for the repo owner — block random fork PRs from consuming CI + if: github.event_name == 'pull_request' && github.actor == 'injectedfusion' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Get changed shell scripts + id: changed + env: + BASE_REF: ${{ github.base_ref }} + run: | + files=$(git diff --name-only --diff-filter=ACMR "origin/$BASE_REF"...HEAD -- '*.sh' | tr '\n' ' ') + echo "files=$files" >> "$GITHUB_OUTPUT" + if [ -n "$files" ]; then + echo "has_files=true" >> "$GITHUB_OUTPUT" + fi + + - name: Install shellcheck + if: steps.changed.outputs.has_files == 'true' + run: sudo apt-get install -y shellcheck + + - name: Run shellcheck on changed scripts + if: steps.changed.outputs.has_files == 'true' + env: + CHANGED_FILES: ${{ steps.changed.outputs.files }} + run: | + exit_code=0 + for f in $CHANGED_FILES; do + [ -f "$f" ] || continue + echo "::group::Checking $f" + shellcheck -S warning "$f" 2>&1 + result=$? + echo "::endgroup::" + if [ $result -ne 0 ]; then + echo "::error file=$f::shellcheck found issues" + exit_code=1 + fi + done + exit $exit_code + + # --- Claude Review (waits for CI on PRs, runs directly on @claude mentions) --- + + review: + needs: [shellcheck] + # Only the repo owner can trigger reviews — prevents billing abuse on public repo + if: | + always() && github.actor == 'injectedfusion' && + ( + (github.event_name == 'pull_request' && + needs.shellcheck.result != 'failure') || + (github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) || + (github.event_name == 'pull_request_review_comment') + ) + runs-on: ubuntu-latest + permissions: + actions: read + contents: write + pull-requests: write + id-token: write + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - uses: anthropics/claude-code-action@v1 + env: + PR_NUMBER: ${{ github.event.pull_request.number }} + REPO: ${{ github.repository }} + with: + claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} + track_progress: true + prompt: | + REPO: ${{ env.REPO }} + PR NUMBER: ${{ env.PR_NUMBER }} + + You are the sole code reviewer for this repository. Your review decision + determines whether this PR merges to the main branch. + + This repo contains reusable pre-commit hooks (bash scripts + .pre-commit-hooks.yaml). + Review this PR focusing on: + - Shell script correctness and safety (quoting, error handling, set -euo pipefail) + - Security (no credential leaks, no unsafe eval/exec patterns) + - Hook configuration validity (.pre-commit-hooks.yaml fields) + - Documentation accuracy (comments match actual behavior) + + After your review: + - Use inline comments for specific code issues. + - Post a single PR comment with your overall summary. + - If the PR is acceptable: approve it with `gh pr review --approve` and + then merge it with `gh pr merge --squash --auto`. + - If the PR has issues that must be fixed: request changes with + `gh pr review --request-changes` and do NOT merge. + + claude_args: | + --model claude-haiku-4-5-20251001 --allowedTools "mcp__github_inline_comment__create_inline_comment,Bash(gh pr comment:*),Bash(gh pr diff:*),Bash(gh pr view:*),Bash(gh pr review:*),Bash(gh pr merge:*)"