From ec6fbb96b30320db1ce0d4648dbdab311419bc8d Mon Sep 17 00:00:00 2001 From: igerber Date: Sun, 25 Jan 2026 16:19:52 -0500 Subject: [PATCH 1/8] Add /push-pr-update skill for committing and pushing PR revisions This skill streamlines the workflow for pushing updates to existing PRs: - Stages and commits local changes with optional custom message - Pushes to the PR branch - Triggers AI code review via /ai-review comment (can be skipped with --no-review) - Includes same secret scanning safeguards as /submit-pr Co-Authored-By: Claude Opus 4.5 --- .claude/commands/push-pr-update.md | 219 +++++++++++++++++++++++++++++ 1 file changed, 219 insertions(+) create mode 100644 .claude/commands/push-pr-update.md diff --git a/.claude/commands/push-pr-update.md b/.claude/commands/push-pr-update.md new file mode 100644 index 00000000..cf126f17 --- /dev/null +++ b/.claude/commands/push-pr-update.md @@ -0,0 +1,219 @@ +--- +description: Push code revisions to an existing PR and trigger AI code review +argument-hint: "[--message ] [--no-review]" +--- + +# Push PR Update + +Push local changes to an existing pull request branch and optionally trigger AI code review. + +## Arguments + +`$ARGUMENTS` may contain: +- `--message ` (optional): Custom commit message. If omitted, auto-generate from changes. +- `--no-review` (optional): Skip triggering AI review after push. + +## Instructions + +### 1. Parse Arguments + +Parse `$ARGUMENTS` to extract: +- **--message**: Custom commit message (everything after `--message` until next flag or end) +- **--no-review**: Boolean flag + +### 2. Validate Current State + +1. **Check current branch**: + ```bash + git branch --show-current + ``` + - If on `main` (or the repository's default branch), abort: + ``` + Error: Cannot push PR update from main branch. + Switch to a feature branch or use /submit-pr to create a new PR. + ``` + +2. **Check for changes to commit**: + ```bash + git status --porcelain + ``` + - If output is empty, abort: + ``` + No changes detected. Working directory is clean. + Nothing to push. + ``` + +3. **Get PR information**: + ```bash + gh pr view --json number,url,headRefName,baseRefName + ``` + - If no PR exists for current branch, abort: + ``` + Error: No open PR found for branch ''. + Use /submit-pr to create a new pull request. + ``` + - Store PR number and URL for later use. + +### 3. Stage and Commit Changes + +1. **Stage all changes**: + ```bash + git add -A + ``` + +2. **Secret scanning check** (same as submit-pr): + - **Run deterministic pattern check** (case-insensitive): + ```bash + git diff --cached | grep -iE "(AKIA[A-Z0-9]{16}|ghp_[a-zA-Z0-9]{36}|sk-[a-zA-Z0-9]{48}|gho_[a-zA-Z0-9]{36}|api[_-]?key\s*[=:]|secret[_-]?key\s*[=:]|password\s*[=:]|private[_-]?key|bearer\s+[a-zA-Z0-9_-]+|token\s*[=:])" || true + ``` + - **Check for sensitive file names**: + ```bash + git diff --cached --name-only | grep -iE "(\.env|credentials|secret|\.pem|\.key|\.p12|\.pfx|id_rsa|id_ed25519)$" || true + ``` + - If patterns detected, **unstage and warn**: + ```bash + git reset HEAD + ``` + Then use AskUserQuestion: + ``` + Warning: Potential secrets detected in files: + - + + Files have been unstaged for safety. + + Options: + 1. Abort - review and remove secrets before retrying + 2. Continue anyway - I confirm these are not real secrets (will re-stage) + ``` + - If user chooses to continue, re-stage with `git add -A` + +3. **Generate or use commit message**: + - If `--message` provided, use that message + - Otherwise, generate from changes: + - Run `git diff --cached --stat` to see what's being committed + - Analyze the changes and generate a descriptive commit message + - Use imperative mood ("Add", "Fix", "Update", "Refactor") + - Format with HEREDOC and Co-Authored-By: + ```bash + git commit -m "$(cat <<'EOF' + + + Co-Authored-By: Claude Opus 4.5 + EOF + )" + ``` + +### 4. Push to Remote + +1. **Push to the tracked remote branch**: + ```bash + git push + ``` + - If push fails (e.g., remote rejected), report error and suggest: + ``` + Push failed: + + If the remote has new commits, try: + git pull --rebase && /push-pr-update + ``` + +2. **Get pushed commit info**: + ```bash + git log -1 --oneline + ``` + +### 5. Trigger AI Review (unless `--no-review`) + +If `--no-review` flag was NOT provided: + +1. **Extract repository owner/repo from remote URL**: + ```bash + git remote get-url origin + ``` + Parse to extract `` and `` from URL (handles both SSH and HTTPS formats). + +2. **Add review comment using MCP tool**: + ``` + mcp__github__add_issue_comment with parameters: + - owner: + - repo: + - issue_number: + - body: "/ai-review" + ``` + +### 6. Report Results + +**If AI review triggered:** +``` +Changes pushed to PR # + +Commit: - +Files changed: + +AI code review triggered. Results will appear shortly. + +PR URL: +``` + +**If `--no-review` was used:** +``` +Changes pushed to PR # + +Commit: - +Files changed: + +PR URL: + +Tip: Run /ai-review to request AI code review. +``` + +## Error Handling + +### Not on a Feature Branch +``` +Error: Cannot push PR update from main branch. +Switch to a feature branch or use /submit-pr to create a new PR. +``` + +### No Changes to Commit +``` +No changes detected. Working directory is clean. +Nothing to push. +``` + +### No Open PR for Branch +``` +Error: No open PR found for branch ''. +Use /submit-pr to create a new pull request. +``` + +### Push Failed +``` +Push failed: + +If the remote has new commits, try: + git pull --rebase && /push-pr-update +``` + +## Examples + +```bash +# Push changes with auto-generated commit message and trigger AI review +/push-pr-update + +# Push with custom commit message +/push-pr-update --message "Address PR feedback: fix edge case handling" + +# Push without triggering AI review +/push-pr-update --no-review + +# Both options together +/push-pr-update --message "Fix typo in docstring" --no-review +``` + +## Notes + +- This skill is for updating existing PRs. Use `/submit-pr` to create new PRs. +- Always stages ALL changes (`git add -A`). Stage manually first for partial commits. +- The `/ai-review` comment triggers the repository's AI review workflow (if configured). +- Uses the same secret scanning as `/submit-pr` to prevent accidental credential commits. From cd19c6fa8a0d4e2410c3ea7b3149775a0ea93536 Mon Sep 17 00:00:00 2001 From: igerber Date: Sun, 25 Jan 2026 16:32:03 -0500 Subject: [PATCH 2/8] Address PR #111 feedback: robust push, dynamic default branch, file count - Fix P2: Check for upstream tracking branch before push; use `git push -u origin HEAD` when no upstream is configured - Fix P3: Dynamically detect default branch using `gh repo view` instead of hardcoding "main" - Fix P3: Capture files changed count after staging for accurate reporting Co-Authored-By: Claude Opus 4.5 --- .claude/commands/push-pr-update.md | 55 +++++++++++++++++++----------- 1 file changed, 36 insertions(+), 19 deletions(-) diff --git a/.claude/commands/push-pr-update.md b/.claude/commands/push-pr-update.md index cf126f17..1aaa4af7 100644 --- a/.claude/commands/push-pr-update.md +++ b/.claude/commands/push-pr-update.md @@ -23,17 +23,23 @@ Parse `$ARGUMENTS` to extract: ### 2. Validate Current State -1. **Check current branch**: +1. **Get repository default branch**: + ```bash + gh repo view --json defaultBranchRef --jq '.defaultBranchRef.name' + ``` + Store as ``. + +2. **Check current branch**: ```bash git branch --show-current ``` - - If on `main` (or the repository's default branch), abort: + - If current branch equals ``, abort: ``` - Error: Cannot push PR update from main branch. + Error: Cannot push PR update from branch. Switch to a feature branch or use /submit-pr to create a new PR. ``` -2. **Check for changes to commit**: +3. **Check for changes to commit**: ```bash git status --porcelain ``` @@ -43,7 +49,7 @@ Parse `$ARGUMENTS` to extract: Nothing to push. ``` -3. **Get PR information**: +4. **Get PR information**: ```bash gh pr view --json number,url,headRefName,baseRefName ``` @@ -61,7 +67,13 @@ Parse `$ARGUMENTS` to extract: git add -A ``` -2. **Secret scanning check** (same as submit-pr): +2. **Capture file count for reporting**: + ```bash + git diff --cached --name-only | wc -l + ``` + Store as `` for use in final report. + +3. **Secret scanning check** (same as submit-pr): - **Run deterministic pattern check** (case-insensitive): ```bash git diff --cached | grep -iE "(AKIA[A-Z0-9]{16}|ghp_[a-zA-Z0-9]{36}|sk-[a-zA-Z0-9]{48}|gho_[a-zA-Z0-9]{36}|api[_-]?key\s*[=:]|secret[_-]?key\s*[=:]|password\s*[=:]|private[_-]?key|bearer\s+[a-zA-Z0-9_-]+|token\s*[=:])" || true @@ -87,7 +99,7 @@ Parse `$ARGUMENTS` to extract: ``` - If user chooses to continue, re-stage with `git add -A` -3. **Generate or use commit message**: +4. **Generate or use commit message**: - If `--message` provided, use that message - Otherwise, generate from changes: - Run `git diff --cached --stat` to see what's being committed @@ -105,19 +117,24 @@ Parse `$ARGUMENTS` to extract: ### 4. Push to Remote -1. **Push to the tracked remote branch**: +1. **Check for upstream tracking branch**: ```bash - git push + git rev-parse --abbrev-ref --symbolic-full-name @{u} 2>/dev/null ``` - - If push fails (e.g., remote rejected), report error and suggest: - ``` - Push failed: - If the remote has new commits, try: - git pull --rebase && /push-pr-update - ``` +2. **Push to remote**: + - If upstream exists: `git push` + - If no upstream: `git push -u origin HEAD` + + If push fails, report error and suggest: + ``` + Push failed: + + If the remote has new commits, try: + git pull --rebase && /push-pr-update + ``` -2. **Get pushed commit info**: +3. **Get pushed commit info**: ```bash git log -1 --oneline ``` @@ -148,7 +165,7 @@ If `--no-review` flag was NOT provided: Changes pushed to PR # Commit: - -Files changed: +Files changed: AI code review triggered. Results will appear shortly. @@ -160,7 +177,7 @@ PR URL: Changes pushed to PR # Commit: - -Files changed: +Files changed: PR URL: @@ -171,7 +188,7 @@ Tip: Run /ai-review to request AI code review. ### Not on a Feature Branch ``` -Error: Cannot push PR update from main branch. +Error: Cannot push PR update from branch. Switch to a feature branch or use /submit-pr to create a new PR. ``` From 567ba716c2bda75b5373948483a1bb39bdd817ae Mon Sep 17 00:00:00 2001 From: igerber Date: Sun, 25 Jan 2026 16:43:32 -0500 Subject: [PATCH 3/8] Address PR #111 feedback round 2: unpushed commits, fork repos, secret leak 1. Handle committed-but-unpushed changes: Clean working tree no longer blocks push when branch has committed changes ahead of upstream. Uses `git rev-list --count @{u}..HEAD` to detect unpushed commits. 2. Fix fork workflow: AI review comment now targets the PR's base repository instead of the fork's origin. Uses `gh pr view --json baseRepository` to get the correct upstream repo. 3. Prevent secret value leaks: Changed from `grep` (which echoes matching content) to `git diff -G ... --name-only` (which only outputs file names, not the matching secret values). Co-Authored-By: Claude Opus 4.5 --- .claude/commands/push-pr-update.md | 34 ++++++++++++++++++------------ 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/.claude/commands/push-pr-update.md b/.claude/commands/push-pr-update.md index 1aaa4af7..02c37e5f 100644 --- a/.claude/commands/push-pr-update.md +++ b/.claude/commands/push-pr-update.md @@ -39,15 +39,21 @@ Parse `$ARGUMENTS` to extract: Switch to a feature branch or use /submit-pr to create a new PR. ``` -3. **Check for changes to commit**: +3. **Check for changes to commit or push**: ```bash git status --porcelain ``` - - If output is empty, abort: - ``` - No changes detected. Working directory is clean. - Nothing to push. - ``` + - If output is empty (working directory clean): + - Check if branch is ahead of upstream: + ```bash + git rev-list --count @{u}..HEAD 2>/dev/null || echo "0" + ``` + - If ahead count > 0: Skip to Section 4 (Push to Remote) — there are committed changes to push + - If ahead count = 0 (or no upstream): Abort: + ``` + No changes detected. Working directory is clean and branch is up to date. + Nothing to push. + ``` 4. **Get PR information**: ```bash @@ -74,10 +80,11 @@ Parse `$ARGUMENTS` to extract: Store as `` for use in final report. 3. **Secret scanning check** (same as submit-pr): - - **Run deterministic pattern check** (case-insensitive): + - **Run deterministic pattern check** (file names only, no content leaked): ```bash - git diff --cached | grep -iE "(AKIA[A-Z0-9]{16}|ghp_[a-zA-Z0-9]{36}|sk-[a-zA-Z0-9]{48}|gho_[a-zA-Z0-9]{36}|api[_-]?key\s*[=:]|secret[_-]?key\s*[=:]|password\s*[=:]|private[_-]?key|bearer\s+[a-zA-Z0-9_-]+|token\s*[=:])" || true + git diff --cached -G "(AKIA[A-Z0-9]{16}|ghp_[a-zA-Z0-9]{36}|sk-[a-zA-Z0-9]{48}|gho_[a-zA-Z0-9]{36}|api[_-]?key\s*[=:]|secret[_-]?key\s*[=:]|password\s*[=:]|private[_-]?key|bearer\s+[a-zA-Z0-9_-]+|token\s*[=:])" --name-only ``` + Note: Uses `-G` to search diff content but `--name-only` to output only file names, preventing secret values from appearing in logs. - **Check for sensitive file names**: ```bash git diff --cached --name-only | grep -iE "(\.env|credentials|secret|\.pem|\.key|\.p12|\.pfx|id_rsa|id_ed25519)$" || true @@ -143,11 +150,12 @@ Parse `$ARGUMENTS` to extract: If `--no-review` flag was NOT provided: -1. **Extract repository owner/repo from remote URL**: +1. **Get base repository from PR**: ```bash - git remote get-url origin + gh pr view --json baseRepository --jq '.baseRepository.owner.login + "/" + .baseRepository.name' ``` - Parse to extract `` and `` from URL (handles both SSH and HTTPS formats). + Store as `/` (this is the upstream repo, correct for fork workflows). + Parse to extract `` and ``. 2. **Add review comment using MCP tool**: ``` @@ -192,9 +200,9 @@ Error: Cannot push PR update from branch. Switch to a feature branch or use /submit-pr to create a new PR. ``` -### No Changes to Commit +### No Changes to Commit or Push ``` -No changes detected. Working directory is clean. +No changes detected. Working directory is clean and branch is up to date. Nothing to push. ``` From 110859f606b167ae616a841d837b2de497460f41 Mon Sep 17 00:00:00 2001 From: igerber Date: Sun, 25 Jan 2026 16:52:18 -0500 Subject: [PATCH 4/8] Address PR #111 feedback round 3: PR lookup order, upstream handling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix two P1 issues from AI code review: 1. Reorder PR lookup before changes check (Section 2.3 ↔ 2.4) - PR lookup now runs BEFORE "skip to Section 4" paths - Ensures PR metadata is always available for reporting 2. Handle missing upstream tracking branch correctly - No upstream + local commits → proceed to push with -u - No upstream + no commits → abort with new error message - Has upstream + ahead → proceed to push - Has upstream + not ahead → abort (existing behavior) Previously, missing upstream caused `git rev-list @{u}..HEAD` to fail, the error was suppressed with `|| echo "0"`, and workflow incorrectly aborted even when local commits existed. Co-Authored-By: Claude Opus 4.5 --- .claude/commands/push-pr-update.md | 52 +++++++++++++++++++----------- 1 file changed, 34 insertions(+), 18 deletions(-) diff --git a/.claude/commands/push-pr-update.md b/.claude/commands/push-pr-update.md index 02c37e5f..8e49c551 100644 --- a/.claude/commands/push-pr-update.md +++ b/.claude/commands/push-pr-update.md @@ -39,23 +39,7 @@ Parse `$ARGUMENTS` to extract: Switch to a feature branch or use /submit-pr to create a new PR. ``` -3. **Check for changes to commit or push**: - ```bash - git status --porcelain - ``` - - If output is empty (working directory clean): - - Check if branch is ahead of upstream: - ```bash - git rev-list --count @{u}..HEAD 2>/dev/null || echo "0" - ``` - - If ahead count > 0: Skip to Section 4 (Push to Remote) — there are committed changes to push - - If ahead count = 0 (or no upstream): Abort: - ``` - No changes detected. Working directory is clean and branch is up to date. - Nothing to push. - ``` - -4. **Get PR information**: +3. **Get PR information**: ```bash gh pr view --json number,url,headRefName,baseRefName ``` @@ -66,6 +50,32 @@ Parse `$ARGUMENTS` to extract: ``` - Store PR number and URL for later use. +4. **Check for changes to commit or push**: + ```bash + git status --porcelain + ``` + - If output is empty (working directory clean): + - Check if branch has an upstream tracking branch: + ```bash + git rev-parse --abbrev-ref @{u} 2>/dev/null + ``` + - If NO upstream exists: + - Check if there are local commits: `git rev-list --count HEAD 2>/dev/null || echo "0"` + - If local commits > 0: Skip to Section 4 (Push to Remote) — will push with `-u` to set upstream + - If no local commits: Abort (new branch with nothing to push): + ``` + No changes detected. Working directory is clean and branch has no commits. + Nothing to push. + ``` + - If upstream EXISTS: + - Check if branch is ahead: `git rev-list --count @{u}..HEAD` + - If ahead count > 0: Skip to Section 4 (Push to Remote) — there are committed changes to push + - If ahead count = 0: Abort: + ``` + No changes detected. Working directory is clean and branch is up to date. + Nothing to push. + ``` + ### 3. Stage and Commit Changes 1. **Stage all changes**: @@ -200,12 +210,18 @@ Error: Cannot push PR update from branch. Switch to a feature branch or use /submit-pr to create a new PR. ``` -### No Changes to Commit or Push +### No Changes to Commit or Push (with upstream) ``` No changes detected. Working directory is clean and branch is up to date. Nothing to push. ``` +### No Changes to Commit or Push (no upstream, no commits) +``` +No changes detected. Working directory is clean and branch has no commits. +Nothing to push. +``` + ### No Open PR for Branch ``` Error: No open PR found for branch ''. From 723da00d0afdd22ad24145e1a36f6fdf49d12d48 Mon Sep 17 00:00:00 2001 From: igerber Date: Sun, 25 Jan 2026 17:01:03 -0500 Subject: [PATCH 5/8] Fix POSIX ERE regex: replace \s with [[:space:]] for secret scanning The \s shorthand doesn't work in POSIX ERE (used by git diff -G and grep -E). Replace with [[:space:]] character class for proper whitespace matching. This fixes the security scan to correctly detect patterns like: - api_key = "secret" - bearer - password : "value" Co-Authored-By: Claude Opus 4.5 --- .claude/commands/push-pr-update.md | 2 +- .claude/commands/submit-pr.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.claude/commands/push-pr-update.md b/.claude/commands/push-pr-update.md index 8e49c551..307fec46 100644 --- a/.claude/commands/push-pr-update.md +++ b/.claude/commands/push-pr-update.md @@ -92,7 +92,7 @@ Parse `$ARGUMENTS` to extract: 3. **Secret scanning check** (same as submit-pr): - **Run deterministic pattern check** (file names only, no content leaked): ```bash - git diff --cached -G "(AKIA[A-Z0-9]{16}|ghp_[a-zA-Z0-9]{36}|sk-[a-zA-Z0-9]{48}|gho_[a-zA-Z0-9]{36}|api[_-]?key\s*[=:]|secret[_-]?key\s*[=:]|password\s*[=:]|private[_-]?key|bearer\s+[a-zA-Z0-9_-]+|token\s*[=:])" --name-only + git diff --cached -G "(AKIA[A-Z0-9]{16}|ghp_[a-zA-Z0-9]{36}|sk-[a-zA-Z0-9]{48}|gho_[a-zA-Z0-9]{36}|api[_-]?key[[:space:]]*[=:]|secret[_-]?key[[:space:]]*[=:]|password[[:space:]]*[=:]|private[_-]?key|bearer[[:space:]]+[a-zA-Z0-9_-]+|token[[:space:]]*[=:])" --name-only ``` Note: Uses `-G` to search diff content but `--name-only` to output only file names, preventing secret values from appearing in logs. - **Check for sensitive file names**: diff --git a/.claude/commands/submit-pr.md b/.claude/commands/submit-pr.md index b98c71b6..48a06fc3 100644 --- a/.claude/commands/submit-pr.md +++ b/.claude/commands/submit-pr.md @@ -136,7 +136,7 @@ Determine if this is a fork-based workflow: 2. **Secret scanning check** (AFTER staging to catch all files): - **Run deterministic pattern check** (case-insensitive with expanded patterns): ```bash - git diff --cached | grep -iE "(AKIA[A-Z0-9]{16}|ghp_[a-zA-Z0-9]{36}|sk-[a-zA-Z0-9]{48}|gho_[a-zA-Z0-9]{36}|api[_-]?key\s*[=:]|secret[_-]?key\s*[=:]|password\s*[=:]|private[_-]?key|bearer\s+[a-zA-Z0-9_-]+|token\s*[=:])" || true + git diff --cached | grep -iE "(AKIA[A-Z0-9]{16}|ghp_[a-zA-Z0-9]{36}|sk-[a-zA-Z0-9]{48}|gho_[a-zA-Z0-9]{36}|api[_-]?key[[:space:]]*[=:]|secret[_-]?key[[:space:]]*[=:]|password[[:space:]]*[=:]|private[_-]?key|bearer[[:space:]]+[a-zA-Z0-9_-]+|token[[:space:]]*[=:])" || true ``` - **Check for sensitive file names** (case-insensitive): ```bash From ff05a0214657dbccba21dbde1a057262631b502b Mon Sep 17 00:00:00 2001 From: igerber Date: Sun, 25 Jan 2026 17:13:29 -0500 Subject: [PATCH 6/8] Address PR #111 round 5: case-insensitive secrets, fix no-commits guard Security (P2): Make secret pattern matching case-insensitive using POSIX character classes. Patterns like api_key now match API_KEY, Api_Key, etc. Token prefixes (AKIA, ghp_, sk_, gho_) remain case-sensitive as intended. Logic (P3): Fix "no commits" guard to compare against default branch. Changed from `git rev-list --count HEAD` (counts ALL commits) to `git rev-list --count ..HEAD` (counts commits unique to this branch). Updated error messages accordingly. Co-Authored-By: Claude Opus 4.5 --- .claude/commands/push-pr-update.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.claude/commands/push-pr-update.md b/.claude/commands/push-pr-update.md index 307fec46..e21dafa3 100644 --- a/.claude/commands/push-pr-update.md +++ b/.claude/commands/push-pr-update.md @@ -60,11 +60,11 @@ Parse `$ARGUMENTS` to extract: git rev-parse --abbrev-ref @{u} 2>/dev/null ``` - If NO upstream exists: - - Check if there are local commits: `git rev-list --count HEAD 2>/dev/null || echo "0"` - - If local commits > 0: Skip to Section 4 (Push to Remote) — will push with `-u` to set upstream - - If no local commits: Abort (new branch with nothing to push): + - Check if branch has commits ahead of default: `git rev-list --count ..HEAD 2>/dev/null || echo "0"` + - If ahead count > 0: Skip to Section 4 (Push to Remote) — will push with `-u` to set upstream + - If ahead count = 0: Abort (new branch with nothing to push): ``` - No changes detected. Working directory is clean and branch has no commits. + No changes detected. Working directory is clean and branch has no commits ahead of . Nothing to push. ``` - If upstream EXISTS: @@ -92,7 +92,7 @@ Parse `$ARGUMENTS` to extract: 3. **Secret scanning check** (same as submit-pr): - **Run deterministic pattern check** (file names only, no content leaked): ```bash - git diff --cached -G "(AKIA[A-Z0-9]{16}|ghp_[a-zA-Z0-9]{36}|sk-[a-zA-Z0-9]{48}|gho_[a-zA-Z0-9]{36}|api[_-]?key[[:space:]]*[=:]|secret[_-]?key[[:space:]]*[=:]|password[[:space:]]*[=:]|private[_-]?key|bearer[[:space:]]+[a-zA-Z0-9_-]+|token[[:space:]]*[=:])" --name-only + git diff --cached -G "(AKIA[A-Z0-9]{16}|ghp_[a-zA-Z0-9]{36}|sk-[a-zA-Z0-9]{48}|gho_[a-zA-Z0-9]{36}|[Aa][Pp][Ii][_-]?[Kk][Ee][Yy][[:space:]]*[=:]|[Ss][Ee][Cc][Rr][Ee][Tt][_-]?[Kk][Ee][Yy][[:space:]]*[=:]|[Pp][Aa][Ss][Ss][Ww][Oo][Rr][Dd][[:space:]]*[=:]|[Pp][Rr][Ii][Vv][Aa][Tt][Ee][_-]?[Kk][Ee][Yy]|[Bb][Ee][Aa][Rr][Ee][Rr][[:space:]]+[a-zA-Z0-9_-]+|[Tt][Oo][Kk][Ee][Nn][[:space:]]*[=:])" --name-only ``` Note: Uses `-G` to search diff content but `--name-only` to output only file names, preventing secret values from appearing in logs. - **Check for sensitive file names**: @@ -216,9 +216,9 @@ No changes detected. Working directory is clean and branch is up to date. Nothing to push. ``` -### No Changes to Commit or Push (no upstream, no commits) +### No Changes to Commit or Push (no upstream, no commits ahead) ``` -No changes detected. Working directory is clean and branch has no commits. +No changes detected. Working directory is clean and branch has no commits ahead of . Nothing to push. ``` From f4de4ede8b6d2bce2b37b921468b6781f4713f06 Mon Sep 17 00:00:00 2001 From: igerber Date: Sun, 25 Jan 2026 17:26:18 -0500 Subject: [PATCH 7/8] Address PR #111 round 6: default branch fallback, push-only secret scan, files count - Fix default branch detection: fall back to origin/ when local branch doesn't exist (handles shallow clones) - Add secret scanning for already-committed changes in push-only path (Section 3a) - previously only staged changes were scanned - Compute in push-only path for accurate reporting - Fix git diff -G exit status: use || true to prevent abort on pattern match - Use variable capture pattern for consistent secret detection handling Co-Authored-By: Claude Opus 4.5 --- .claude/commands/push-pr-update.md | 51 ++++++++++++++++++++++++++---- 1 file changed, 44 insertions(+), 7 deletions(-) diff --git a/.claude/commands/push-pr-update.md b/.claude/commands/push-pr-update.md index e21dafa3..9db38e01 100644 --- a/.claude/commands/push-pr-update.md +++ b/.claude/commands/push-pr-update.md @@ -60,8 +60,15 @@ Parse `$ARGUMENTS` to extract: git rev-parse --abbrev-ref @{u} 2>/dev/null ``` - If NO upstream exists: - - Check if branch has commits ahead of default: `git rev-list --count ..HEAD 2>/dev/null || echo "0"` - - If ahead count > 0: Skip to Section 4 (Push to Remote) — will push with `-u` to set upstream + - Determine comparison ref (handles shallow clones where local branch may not exist): + - If `` exists locally (`git rev-parse --verify 2>/dev/null`): use `` + - Otherwise: use `origin/` + - Store as `` + - Check if branch has commits ahead: `git rev-list --count ..HEAD 2>/dev/null || echo "0"` + - If ahead count > 0: + - **Scan for secrets in commits to push** (see Section 3a below) + - Compute ``: `git diff --name-only ..HEAD | wc -l` + - Skip to Section 4 (Push to Remote) — will push with `-u` to set upstream - If ahead count = 0: Abort (new branch with nothing to push): ``` No changes detected. Working directory is clean and branch has no commits ahead of . @@ -69,13 +76,43 @@ Parse `$ARGUMENTS` to extract: ``` - If upstream EXISTS: - Check if branch is ahead: `git rev-list --count @{u}..HEAD` - - If ahead count > 0: Skip to Section 4 (Push to Remote) — there are committed changes to push + - If ahead count > 0: + - **Scan for secrets in commits to push** (see Section 3a below) + - Compute ``: `git diff --name-only @{u}..HEAD | wc -l` + - Skip to Section 4 (Push to Remote) — there are committed changes to push - If ahead count = 0: Abort: ``` No changes detected. Working directory is clean and branch is up to date. Nothing to push. ``` +### 3a. Secret Scan for Already-Committed Changes (when skipping Section 3) + +When the working tree is clean but commits are ahead, scan for secrets in the commits to be pushed before proceeding to Section 4: + +1. **Get diff range**: Use `..HEAD` (from Section 2.4 — either `@{u}`, ``, or `origin/`) + +2. **Run pattern check** (file names only, no content leaked): + ```bash + secret_files=$(git diff ..HEAD -G "(AKIA[A-Z0-9]{16}|ghp_[a-zA-Z0-9]{36}|sk-[a-zA-Z0-9]{48}|gho_[a-zA-Z0-9]{36}|[Aa][Pp][Ii][_-]?[Kk][Ee][Yy][[:space:]]*[=:]|[Ss][Ee][Cc][Rr][Ee][Tt][_-]?[Kk][Ee][Yy][[:space:]]*[=:]|[Pp][Aa][Ss][Ss][Ww][Oo][Rr][Dd][[:space:]]*[=:]|[Pp][Rr][Ii][Vv][Aa][Tt][Ee][_-]?[Kk][Ee][Yy]|[Bb][Ee][Aa][Rr][Ee][Rr][[:space:]]+[a-zA-Z0-9_-]+|[Tt][Oo][Kk][Ee][Nn][[:space:]]*[=:])" --name-only 2>/dev/null || true) + ``` + +3. **Check for sensitive file names**: + ```bash + sensitive_files=$(git diff --name-only ..HEAD | grep -iE "(\.env|credentials|secret|\.pem|\.key|\.p12|\.pfx|id_rsa|id_ed25519)$" || true) + ``` + +4. **If patterns detected**, warn with AskUserQuestion: + ``` + Warning: Potential secrets detected in committed changes: + - + + These changes are already committed. Options: + 1. Abort - use 'git reset --soft HEAD~N' to uncommit and remove secrets before retrying + 2. Continue anyway - I confirm these are not real secrets + ``` + Note: Unlike Section 3, we cannot simply unstage these changes since they are already committed. + ### 3. Stage and Commit Changes 1. **Stage all changes**: @@ -92,14 +129,14 @@ Parse `$ARGUMENTS` to extract: 3. **Secret scanning check** (same as submit-pr): - **Run deterministic pattern check** (file names only, no content leaked): ```bash - git diff --cached -G "(AKIA[A-Z0-9]{16}|ghp_[a-zA-Z0-9]{36}|sk-[a-zA-Z0-9]{48}|gho_[a-zA-Z0-9]{36}|[Aa][Pp][Ii][_-]?[Kk][Ee][Yy][[:space:]]*[=:]|[Ss][Ee][Cc][Rr][Ee][Tt][_-]?[Kk][Ee][Yy][[:space:]]*[=:]|[Pp][Aa][Ss][Ss][Ww][Oo][Rr][Dd][[:space:]]*[=:]|[Pp][Rr][Ii][Vv][Aa][Tt][Ee][_-]?[Kk][Ee][Yy]|[Bb][Ee][Aa][Rr][Ee][Rr][[:space:]]+[a-zA-Z0-9_-]+|[Tt][Oo][Kk][Ee][Nn][[:space:]]*[=:])" --name-only + secret_files=$(git diff --cached -G "(AKIA[A-Z0-9]{16}|ghp_[a-zA-Z0-9]{36}|sk-[a-zA-Z0-9]{48}|gho_[a-zA-Z0-9]{36}|[Aa][Pp][Ii][_-]?[Kk][Ee][Yy][[:space:]]*[=:]|[Ss][Ee][Cc][Rr][Ee][Tt][_-]?[Kk][Ee][Yy][[:space:]]*[=:]|[Pp][Aa][Ss][Ss][Ww][Oo][Rr][Dd][[:space:]]*[=:]|[Pp][Rr][Ii][Vv][Aa][Tt][Ee][_-]?[Kk][Ee][Yy]|[Bb][Ee][Aa][Rr][Ee][Rr][[:space:]]+[a-zA-Z0-9_-]+|[Tt][Oo][Kk][Ee][Nn][[:space:]]*[=:])" --name-only 2>/dev/null || true) ``` - Note: Uses `-G` to search diff content but `--name-only` to output only file names, preventing secret values from appearing in logs. + Note: Uses `-G` to search diff content but `--name-only` to output only file names, preventing secret values from appearing in logs. The `|| true` prevents exit status 1 when patterns match from aborting strict runners. - **Check for sensitive file names**: ```bash - git diff --cached --name-only | grep -iE "(\.env|credentials|secret|\.pem|\.key|\.p12|\.pfx|id_rsa|id_ed25519)$" || true + sensitive_files=$(git diff --cached --name-only | grep -iE "(\.env|credentials|secret|\.pem|\.key|\.p12|\.pfx|id_rsa|id_ed25519)$" || true) ``` - - If patterns detected, **unstage and warn**: + - **If patterns detected** (i.e., `secret_files` or `sensitive_files` is non-empty), **unstage and warn**: ```bash git reset HEAD ``` From d5b7890beba5f76ac8ba7c73c3eab2ccf0e8a844 Mon Sep 17 00:00:00 2001 From: igerber Date: Sun, 25 Jan 2026 17:39:04 -0500 Subject: [PATCH 8/8] Address PR #111 round 7: verify origin ref exists, fix submit-pr secret leak - push-pr-update: Add fallback to fetch origin/ when neither local nor remote ref exists in single-branch clones - submit-pr: Switch secret scan from piping diff content to using -G with --name-only to prevent secrets from appearing in logs Co-Authored-By: Claude Opus 4.5 --- .claude/commands/push-pr-update.md | 5 +++-- .claude/commands/submit-pr.md | 7 ++++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/.claude/commands/push-pr-update.md b/.claude/commands/push-pr-update.md index 9db38e01..d75e32f1 100644 --- a/.claude/commands/push-pr-update.md +++ b/.claude/commands/push-pr-update.md @@ -60,9 +60,10 @@ Parse `$ARGUMENTS` to extract: git rev-parse --abbrev-ref @{u} 2>/dev/null ``` - If NO upstream exists: - - Determine comparison ref (handles shallow clones where local branch may not exist): + - Determine comparison ref (handles shallow/single-branch clones): - If `` exists locally (`git rev-parse --verify 2>/dev/null`): use `` - - Otherwise: use `origin/` + - Else if `origin/` exists (`git rev-parse --verify origin/ 2>/dev/null`): use `origin/` + - Else: fetch it first (`git fetch origin --depth=1 2>/dev/null || true`), then use `origin/` - Store as `` - Check if branch has commits ahead: `git rev-list --count ..HEAD 2>/dev/null || echo "0"` - If ahead count > 0: diff --git a/.claude/commands/submit-pr.md b/.claude/commands/submit-pr.md index 48a06fc3..08edf2f5 100644 --- a/.claude/commands/submit-pr.md +++ b/.claude/commands/submit-pr.md @@ -134,10 +134,11 @@ Determine if this is a fork-based workflow: ``` 2. **Secret scanning check** (AFTER staging to catch all files): - - **Run deterministic pattern check** (case-insensitive with expanded patterns): + - **Run deterministic pattern check** (file names only, no content leaked): ```bash - git diff --cached | grep -iE "(AKIA[A-Z0-9]{16}|ghp_[a-zA-Z0-9]{36}|sk-[a-zA-Z0-9]{48}|gho_[a-zA-Z0-9]{36}|api[_-]?key[[:space:]]*[=:]|secret[_-]?key[[:space:]]*[=:]|password[[:space:]]*[=:]|private[_-]?key|bearer[[:space:]]+[a-zA-Z0-9_-]+|token[[:space:]]*[=:])" || true + secret_files=$(git diff --cached -G "(AKIA[A-Z0-9]{16}|ghp_[a-zA-Z0-9]{36}|sk-[a-zA-Z0-9]{48}|gho_[a-zA-Z0-9]{36}|[Aa][Pp][Ii][_-]?[Kk][Ee][Yy][[:space:]]*[=:]|[Ss][Ee][Cc][Rr][Ee][Tt][_-]?[Kk][Ee][Yy][[:space:]]*[=:]|[Pp][Aa][Ss][Ss][Ww][Oo][Rr][Dd][[:space:]]*[=:]|[Pp][Rr][Ii][Vv][Aa][Tt][Ee][_-]?[Kk][Ee][Yy]|[Bb][Ee][Aa][Rr][Ee][Rr][[:space:]]+[a-zA-Z0-9_-]+|[Tt][Oo][Kk][Ee][Nn][[:space:]]*[=:])" --name-only 2>/dev/null || true) ``` + Note: Uses `-G` to search diff content but `--name-only` to output only file names, preventing secret values from appearing in logs. The `|| true` prevents exit status 1 when patterns match from aborting strict runners. - **Check for sensitive file names** (case-insensitive): ```bash git diff --cached --name-only | grep -iE "(\.env|credentials|secret|\.pem|\.key|\.p12|\.pfx|id_rsa|id_ed25519)$" || true @@ -151,7 +152,7 @@ Determine if this is a fork-based workflow: ```bash git diff --cached --name-only --diff-filter=A ``` - - If pattern check returns matches or sensitive files detected, **unstage and warn**: + - **If patterns detected** (i.e., `secret_files` or sensitive file names non-empty), **unstage and warn**: ```bash git reset HEAD # Unstage all files ```