Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
110 changes: 110 additions & 0 deletions .github/workflows/pr-pipeline.yml
Original file line number Diff line number Diff line change
@@ -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:*)"
13 changes: 13 additions & 0 deletions .pre-commit-hooks.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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]
41 changes: 41 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ Reusable [pre-commit](https://pre-commit.com/) hooks for GitOps, security, and m
| `check-branch-staleness` | Fail if branch is behind the default branch. Prevents stale commits in multi-agent or team workflows. |
| `trivy-deps` | Scan dependency lockfiles for HIGH/CRITICAL CVEs. Catches what `trivy config` misses. |
| `no-hardcoded-secrets` | Detect hardcoded passwords and API keys in YAML files. |
| `require-signed-commits` | Block commits where `commit.gpgsign` is not `true` or `user.signingkey` is unset. |

## Usage

Expand All @@ -30,6 +31,46 @@ pip install pre-commit # if not already installed
pre-commit install
```

## Personal hooks without per-repo setup

Some hooks (like `require-signed-commits`) enforce personal discipline that shouldn't be imposed on teammates. Use a global git hook instead — fires on every repo with zero per-repo setup:

```bash
mkdir -p ~/.config/git/hooks
git config --global core.hooksPath ~/.config/git/hooks
```

Create `~/.config/git/hooks/pre-commit`:

```bash
#!/usr/bin/env bash
set -euo pipefail

# Personal check (e.g. signing)
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"
exit 1
fi
signingkey="$(git config --get user.signingkey 2>/dev/null || echo '')"
if [[ -z "$signingkey" ]]; then
echo "✗ Unsigned commit blocked: user.signingkey is not set"
exit 1
fi

# Chain to repo pre-commit config if present

Choose a reason for hiding this comment

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

medium

The current comment is slightly ambiguous. The script that follows will execute only the first configuration file it finds, preferring .local.yaml over the standard .pre-commit-config.yaml. The use of a for loop with exec can be misleading, as it might imply iteration over all found files. Updating the comment to clarify this behavior would improve the script's readability for users who copy it.

Suggested change
# Chain to repo pre-commit config if present
# Chain to repo pre-commit config if present, running the first one found (prefers .local.yaml)

repo_root="$(git rev-parse --show-toplevel)"
for config in "$repo_root/.pre-commit-config.local.yaml" "$repo_root/.pre-commit-config.yaml"; do
if [[ -f "$config" ]]; then
exec pre-commit run --config "$config" --hook-stage pre-commit
fi
done
```

```bash
chmod +x ~/.config/git/hooks/pre-commit
```

## Hook Details

### check-branch-staleness
Expand Down
39 changes: 39 additions & 0 deletions hooks/require-signed-commits.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#!/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.
#
# Fails unless commit.gpgsign is explicitly set to true and user.signingkey is configured.

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 <your-key>"
echo ""
echo "If using 1Password SSH agent:"
echo " git config --global gpg.format ssh"
echo " git config --global user.signingkey <your-public-key>"
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 '')"

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 <your-key>"
exit 1
fi

exit 0