push-ci
Push & CI Monitor
Push to remote with user approval, then monitor CI run until completion.
Authorization
⚠️ This skill is the ONLY authorized path for Claude to execute `git push`.
⚠️ All other skills and rules MUST output push commands only (not execute).
⚠️ Push REQUIRES explicit user approval via AskUserQuestion — no exceptions.
| Rule | This Skill | All Other Skills |
|---|---|---|
git push |
Execute (after user approval) | Forbidden (output only) |
git push --force |
Forbidden | Forbidden |
| Push to protected branches (main/master/develop/release/*) | Warn + pre-approval via AskUserQuestion (terminal hook is final gate) | Forbidden |
Defense in Depth: Push Safety
| Layer | Mechanism | Scope | Reliability |
|---|---|---|---|
| L1: git pre-push hook | pre-push-gate.sh reads /dev/tty for terminal confirmation |
Protected branches + non-fast-forward detection | Immune to Claude Code permission caching |
| L2: AskUserQuestion | In-session advisory prompt before push | All pushes | May be auto-approved by session caching (advisory only) |
| L3: git-workflow rules | Claude forbidden from raw git push |
All contexts | Behavioral enforcement |
Primary gate: pre-push-gate.sh (install via /install-scripts). This script runs as a git pre-push hook and prompts the developer directly via /dev/tty, which is immune to Claude Code's permission caching. AskUserQuestion remains as an advisory UX layer but is not the authorization gate — the git hook is.
Why AskUserQuestion alone is insufficient: Session permission caching can auto-approve AskUserQuestion calls in long-running sessions, especially with -c continue mode. See GitHub Issue #15400.
Workflow
sequenceDiagram
participant C as Claude
participant U as User
participant GH as GitHub
C->>C: Phase 0: Preflight
alt Protected branch detected
C->>U: ⚠️ Warning + ask pre-approval
U->>C: Continue / Abort
end
C->>U: Phase 1: Show push plan + ask approval
U->>C: Approve / Reject
alt Approved
C->>GH: Execute git push
C->>GH: Phase 2: gh run watch --exit-status
GH-->>C: CI result
C->>U: Phase 3: Verdict + failure logs
else Rejected
C->>U: Abort (no push)
end
Phase 0: Preflight
Run all checks. Hard-abort on infrastructure failures; warn-and-confirm on protected branches.
# 1. Current branch
BRANCH=$(git rev-parse --abbrev-ref HEAD)
# 2. Protected branch detection
# If main, master, develop, or release/* → warn + AskUserQuestion pre-approval
# (do NOT hard-abort; let user decide)
# 3. Remote exists
git ls-remote --exit-code origin >/dev/null 2>&1
# 4. Working tree status
git status --short
# 5. Commits ahead of remote
git rev-list --count origin/$BRANCH..HEAD 2>/dev/null || echo "new branch"
# 6. Local HEAD SHA (for CI run matching later)
HEAD_SHA=$(git rev-parse HEAD)
| Check | Pass | Fail |
|---|---|---|
| Branch is not protected | Continue | Warn + AskUserQuestion (see below) |
| Remote exists | Continue | Abort: "No remote 'origin' configured" |
| Has commits ahead | Continue | Abort: "Nothing to push (0 commits ahead)" |
Protected branch pre-approval flow (advisory — terminal hook remains final gate):
When branch is main, master, develop, or release/*:
- Show warning with branch name and commit count
- Use AskUserQuestion with options:
- "Continue — push to
<branch>" — proceed to Phase 1 - "Abort" — stop immediately
- "Continue — push to
- If user aborts → stop. If user continues → proceed to Phase 1 (push approval asked separately). Note: the git pre-push hook will still require terminal confirmation via
/dev/ttyas the final authorization gate.
Phase 1: Push Plan + User Approval
Present push summary and ask user for explicit approval using AskUserQuestion:
## Push Plan
- Branch: `<branch>`
- Remote: `origin`
- Commits: <N> ahead
- HEAD: `<sha>`
Command to execute: `git push origin <branch>`
Gate: Use AskUserQuestion with options:
- "Approve push" — proceed to execute
- "Abort" — stop, do not push
If user rejects → stop immediately. Do NOT retry or persuade.
Phase 2: Execute Push + Monitor CI
After user approval:
Command assembly (deterministic):
# 1. Build and execute push command (ONLY after explicit approval)
# ⚠️ Always unset ALLOW_PUSH_PROTECTED to prevent env inheritance bypassing the hook.
# Only set ALLOW_FORCE_WITH_LEASE when --force-with-lease is explicitly requested.
if [[ "$FORCE_WITH_LEASE" == "true" ]]; then
if [[ "$SET_UPSTREAM" == "true" ]]; then
ALLOW_PUSH_PROTECTED= ALLOW_FORCE_WITH_LEASE=1 git push --force-with-lease -u origin "$BRANCH"
else
ALLOW_PUSH_PROTECTED= ALLOW_FORCE_WITH_LEASE=1 git push --force-with-lease origin "$BRANCH"
fi
else
if [[ "$SET_UPSTREAM" == "true" ]]; then
ALLOW_PUSH_PROTECTED= ALLOW_FORCE_WITH_LEASE= git push -u origin "$BRANCH"
else
ALLOW_PUSH_PROTECTED= ALLOW_FORCE_WITH_LEASE= git push origin "$BRANCH"
fi
fi
# If push fails (non-zero exit) → stop immediately, report error, do NOT proceed to CI
# 2. Find CI runs matching HEAD_SHA (only if push succeeded)
RUNS=$(gh run list --branch "$BRANCH" --limit 10 --json databaseId,headSha,status,name \
--jq "[.[] | select(.headSha == \"$HEAD_SHA\")]")
# 3. Monitor all matching runs (worst result = overall verdict)
# For each run in RUNS:
# gh run watch <run-id> --exit-status
# Record pass/fail per run
# If RUNS is empty after 30s retry (3 attempts, 10s interval) → report no CI
# Overall verdict = worst individual result (any fail → overall fail)
# 4. Timeout enforcement
# Track START_TIME=$(date +%s) before watch loop.
# After each poll, check: if (now - START_TIME) > TIMEOUT_MINUTES*60 → stop, report ⚠️ Timeout.
--set-upstream auto-detect: If git rev-parse --abbrev-ref --symbolic-full-name @{u} fails (no upstream), add -u automatically.
CI Run Selection — match by headSha + branch, not "latest" (see step 2 above).
Multiple CI runs: If multiple workflow runs match the same SHA (e.g. CI + Auto Release), monitor all of them in parallel. Report verdict for each run individually. Overall verdict is the worst result across all runs.
If no run found after 30 seconds, retry up to 3 times (10s interval). If still not found:
⚠️ No CI run detected for SHA <sha>. Possible causes:
- No workflow configured for this branch
- Path-filtered workflow didn't trigger
- Check: gh run list --branch <branch>
Timeout: Default 10 minutes. Configurable via --timeout <minutes>.
Phase 3: Verdict
| CI Result | Action |
|---|---|
| Pass | Output: "✅ CI passed — <run-url>" |
| Fail | Output: failing jobs + gh run view <id> --log-failed summary |
| Timeout | Output: "⚠️ CI still running after min — gh run watch <id>" |
Arguments
| Argument | Description | Default |
|---|---|---|
--timeout <min> |
CI watch timeout in minutes | 10 |
--force-with-lease |
Use --force-with-lease instead of regular push |
off |
--set-upstream |
Add -u flag (first push of new branch) |
auto-detect |
--force is NOT supported. Force push is always forbidden.
Prohibited
- Executing git push WITHOUT prior user approval via AskUserQuestion
- Suggesting or executing git push --force (ever)
- Pushing to protected branches WITHOUT explicit user pre-approval via AskUserQuestion
- Setting ALLOW_PUSH_PROTECTED=1 (this skill must NEVER set this env var; it is reserved for manual developer use only)
- Auto-triggering this skill (disable-model-invocation: true)
- Skipping preflight checks
- Monitoring wrong CI run (must match HEAD SHA)
Verification
- Preflight passed (branch + remote + commits)
- User approved push via AskUserQuestion
- Push executed successfully
- CI run matched by HEAD SHA (not "latest")
- Verdict reported (pass/fail/timeout)
Examples
Input: /push-ci
Phase 0: Preflight — branch feat/auth, 3 commits ahead, remote OK
Phase 1: Show plan → user approves
Phase 2: git push origin feat/auth → gh run watch 12345 --exit-status
Phase 3: ✅ CI passed — https://github.com/.../actions/runs/12345
Input: /push-ci --timeout 15
Phase 0-1: Same as above
Phase 2: Monitor with 15-minute timeout
Phase 3: Verdict
Input: /push-ci --force-with-lease
Phase 0: Preflight (warns on protected branches)
Phase 1: Show plan with --force-with-lease → user approves
Phase 2: git push --force-with-lease origin feat/rebase-cleanup
Phase 3: CI monitoring
Input: /push-ci (on main branch)
Phase 0: Preflight — ⚠️ "main is a protected branch" → AskUserQuestion pre-approval
User: Continue → proceed
Phase 1: Show plan → user approves push
Phase 2: git push origin main → gh run watch
Phase 3: ✅ CI passed