safe-push · v1.1.0 · 2026-04-10 · sha256 ce926fead3518f72
safe-push v1.1.0A
Immutable. This exact content is served forever at /api/v1/blob/ce926fead3518f72.
---
name: safe-push
description: "Pre-push hygiene check for GitHub repositories. Scans for PII, secrets, and sensitive data before pushing. Audits commit messages, enforces repo-specific blocklists, and rate-limits pushes to avoid GitHub abuse detection. Use before any git push, especially to public repos."
license: MIT
origin: custom
author: Rebecca Rae Barton
author_url: https://github.com/thatrebeccarae
metadata:
version: 1.1.0
category: devops
domain: git
updated: 2026-03-20
tested: 2026-03-20
tested_with: "Claude Code v2.1"
---
# Safe Push
Pre-push hygiene check for GitHub repositories. Use when the user asks to push code, especially to public repos.
## Trigger
When the user says "push", "safe push", "push to GitHub", or runs `/safe-push`.
## Procedure
### 1. Classify the repo
```bash
# Check if public repo
git remote -v
# Check for .public-repo marker
test -f .public-repo && echo "PUBLIC" || echo "private or unmarked"
```
If public (or pushing to a public remote): apply ALL checks below.
If private: apply only the PII scan (step 2).
### 2. PII and secrets scan
**Default mode** — scan the diff against the target branch:
```bash
git diff origin/main...HEAD
```
**Full repo mode** (`/safe-push --full`) — scan ALL tracked files, not just the diff. Use this for baseline audits, first-time pushes of existing repos, or periodic hygiene checks. Searches the entire working tree for blocked patterns:
```bash
git ls-files | xargs grep -n -E 'PATTERN' --include='*.md' --include='*.py' --include='*.js' --include='*.ts' --include='*.html' --include='*.sh' --include='*.json' --include='*.yml' --include='*.yaml'
```
Check for:
- Email addresses (personal or client)
- Phone numbers
- API keys, tokens, secrets (AWS, GitHub, Slack, Telegram, generic)
- Private IP addresses, internal hostnames
- Private key material
- Client names or internal project codenames
- Hardcoded credentials or passwords
**Client names and internal project codenames:** Maintain a per-user blocklist at `~/.claude/safe-push-blocklist` (one pattern per line) and load it into the scan. Never hardcode real client names inside this skill file — that would defeat the purpose of the scan.
**Infrastructure patterns (always blocked in public repos):**
- Private IP ranges: Tailscale (100.64.0.0/10), RFC 1918 LAN (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16)
- Hostnames and `.local` / `.lan` / `.internal` TLDs for any machine you run
- Internal directory names specific to your workstation (home dir subfolders, sync folder names)
- VLAN and network segment names
- Self-hosted service names (reverse proxies, databases, dashboards, workflow automation, file sync tools) when appearing in infra context
- Device and hardware serials (e.g., `^[A-Z0-9]{7,12}$`-shaped identifiers in config/doc files)
- Hardware model names in personal-infra context
- Docker paths, `.env` file references
- Slack bot tokens (`xoxb-`), Slack channel IDs (`C` + 10 alphanumerics)
- API keys and credentials for any self-hosted service
- SSH config details, private port mappings
- Hardcoded tracking IDs: GA4 measurement IDs (`G-XXXXXXXXXX`), GTM container IDs (`GTM-XXXXXXX`)
Load your personal patterns from `~/.claude/safe-push-blocklist` rather than embedding them here.
If anything is found:
- List each finding with file, line number, and what was detected
- Ask the user to fix before proceeding
- Do NOT push until resolved
### 3. Commit message audit
Review ALL commit messages in the push range:
```bash
git log origin/main..HEAD --format="%h %s"
```
For public repos, flag:
- Any pattern from your `~/.claude/safe-push-blocklist` (client names, internal codenames)
- Infrastructure references: hostnames, VLAN names, internal paths, private IPs, self-hosted service names, device identifiers
- Hardware identifiers (model numbers, serials, device IDs)
- Internal URLs, private IPs, port numbers tied to services
- Personal info (email, phone, address)
- Vague messages ("fix", "update", "wip") — suggest rewrites
If issues found, suggest interactive rebase to clean messages (with user approval).
### 4. Staggered push (rate limiting)
To avoid triggering GitHub bulk action / automation abuse detection:
- If pushing a single branch with < 50 commits: push normally
- If pushing multiple branches or > 50 commits:
- Push one branch at a time
- Wait 5 seconds between branch pushes
- For very large pushes (100+ commits), break into batches of 50 and wait 10 seconds between batches
- If creating a new repo and pushing initial content with multiple branches:
- Push main/default branch first
- Wait 10 seconds
- Push remaining branches one at a time with 5-second gaps
- NEVER use `git push --all` or `git push --mirror` to a public remote without staggering
```bash
# Example staggered multi-branch push
for branch in main develop feature/foo; do
git push origin "$branch"
sleep 5
done
```
### 5. Final confirmation
Before executing the push, present a summary:
- Repository: name and public/private status
- Branch(es) being pushed
- Number of commits
- Any warnings from steps 2-4
- Push strategy (direct or staggered)
Wait for explicit user confirmation before pushing.
### 6. Push and verify
After pushing:
```bash
git push origin <branch>
# Verify
git log origin/<branch> --oneline -5
```
Report success and the remote URL.
## Configuration files
- **User-level blocklist** — `~/.claude/safe-push-blocklist`: one regex per line, personal strings that must never appear in public commits (client names, your hostnames, your internal paths). Load and scan against this list on every public push.
- **Repo-local `.pii-allowlist`** — one regex per line, matches are excluded from the PII scan (used to allow false positives like example keys in docs)
- **Repo-local `.commit-msg-blocklist`** — terms that should never appear in public commit messages for this repo specifically
## Notes
- This skill does NOT bypass the global pre-commit hook — they work together
- For projects with a public/private repo split (dev mirror → public release): always push to the dev repo first, sync via your sync script, then safe-push the public repo
- When in doubt, treat a repo as public