Skip to content
Development
Skill

/push-ci

Push to remote and monitor CI. Validates branch safety, executes git push WITH explicit user approval, then monitors CI run status via gh CLI. Use when: user says 'push', 'push and watch CI', 'ship it', 'push-ci'. Not for: committing (use /smart-commit), creating PRs (use

From plugin
sd0x-dev-flow
18899 skills16 agents5 hooks
Install
$ npx -y skills add sd0xdev/sd0x-dev-flow --skill push-ci --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/push-ci

Context preview

The summary Claude sees to decide when to auto-load this skill.

Push to remote and monitor CI. Validates branch safety, executes git push WITH explicit user approval, then monitors CI run status via gh CLI. Use when: user says 'push', 'push and watch CI', 'ship it', 'push-ci'. Not for: committing (use /smart-commit), creating PRs (use

SKILL.md

push-ci.SKILL.md
name: push-ci
description: "Push to remote and monitor CI. Validates branch safety, executes git push WITH explicit user approval, then monitors CI run status via gh CLI. Use when: user says 'push', 'push and watch CI', 'ship it', 'push-ci'. Not for: committing (use /smart-commit), creating PRs (use /create-pr), merging (use /merge-prep)."
disable-model-invocation: true
allowed-tools: Bash(git:*), Bash(gh:*), Bash(bash:*), Bash(/bin/bash:*), Read, Grep, Glob, AskUserQuestion

Push & CI Monitor

Push to remote with user approval, then monitor CI run until completion.

Authorization

⚠️ This skill is one of two authorized paths for Claude to execute `git push`.
⚠️ The other is /epic-merge (--force-with-lease for stacked PR chains, per-iteration AskUserQuestion gate).
⚠️ This skill may also use --force-with-lease, but only when the caller passes the flag — and NEVER onto a protected branch; bare --force is forbidden everywhere.
⚠️ All other skills and rules MUST output push commands only (not execute).
⚠️ Push REQUIRES explicit user approval via AskUserQuestion — no exceptions.

| Rule | This Skill | `/epic-merge` | All Other Skills | |------|-----------|---------------|------------------| | `git push` | Execute (after user approval) | Forbidden (uses `--force-with-lease` only) | Forbidden (output only) | | `git push --force` | Forbidden | Forbidden | Forbidden | | `git push --force-with-lease` | Execute — **only** when `--force-with-lease` is explicitly passed, after user approval naming the force form; **never onto a protected branch** (Phase 0 hard-aborts, Phase 2 re-asserts) | Execute (after per-iteration AskUserQuestion) | Forbidden | | Push to protected branches (main/master/develop/release/*) | Warn + pre-approval via AskUserQuestion (final gate is the terminal hook when installed, otherwise this approval); with `--force-with-lease` → **hard abort**, no question asked | Protected PR heads rejected — Phase 0 validation, re-asserted before Step 5 and Rollback (a PR head is not inherently unprotected) | Forbidden |

Defense in Depth: Push Safety

| Layer | Mechanism | Scope | Reliability | |-------|-----------|-------|-------------| | **L1: git pre-push hook** (opt-in) | `pre-push-gate.sh` reads `/dev/tty` for terminal confirmation | **Two classes reach the prompt** (since 2026-08-21): a **protected branch** with `ALLOW_PUSH_PROTECTED` unset, and a **history-rewriting push** whose rewritten refs are not already covered by that first prompt, with `ALLOW_FORCE_UNSHARED` unset. Non-fast-forward is an earlier, orthogonal refusal — this hook's own `exit 1` only when git hands it the ref (the force form); otherwise git refuses first and this hook never sees it (§ below). Skipped by `ALLOW_FORCE_WITH_LEASE=1`, after which the push falls through to the **unshared attestation first** and the protected check second — not straight to the protected check (`scripts/pre-push-gate.sh`: the refusal at the force-form check, then the rewrite gate, then the protected gate) | Immune to Claude Code permission caching — **when installed**, and only for the classes it prompts on | | **L2: AskUserQuestion** | In-session prompt before push | All pushes | May be auto-approved by session caching | | **L3: git-workflow rules** | Claude forbidden from raw `git push` | All contexts | Behavioral enforcement |

**Which layer authorizes depends on whether L1 is installed.** The `pre-push` hook is opt-in (`/codex-setup init --with-push-gate`, or `sync --with-push-gate` on an existing project). `/install-scripts` copies `pre-push-gate.sh` into `.claude/scripts/` and never wires up a hook, so having run it is not evidence the gate exists.

**L1 prompts for two classes of push, and they are different questions.** The **protected** prompt asks *may this branch be pushed to at all*; it fires when the ref set includes a protected branch and `ALLOW_PUSH_PROTECTED` is unset. The **unshared attestation** asks *is anybody else working on the refs this push rewrites*; it fires when the push **rewrites a ref** and `ALLOW_FORCE_UNSHARED` is unset, over the rewritten refs the protected prompt will not already cover. **Rewrite is read per ref class, because ancestry is the *branch* rule**: a branch update is a rewrite when the remote tip is not provably an ancestor of what replaces it, while **every update to an existing tag is one** — git requires force semantics for any change to an existing `refs/tags/*` ref, forward moves included, because a tag names one commit rather than a line of history (`scripts/pre-push-gate.sh` § `is_tag_ref`, and the hook prints a separate line for tags precisely because a forced tag update can be a textbook fast-forward). A tag *creation* has no history to overwrite and is not asked about, and neither is a **deletion** of any ref class — the gate's rewrite test needs a non-null OID on both sides, so removing an existing tag or branch reaches no prompt (`rules/git-workflow.md` § Push safety states the boundary; the maintainer decided on 2026-08-22 to leave it there). Reading the class as "non-fast-forward" alone would wave through exactly the moves git itself classifies as forced — which is every rewritten ref when `ALLOW_PUSH_PROTECTED=1` has silenced that prompt. Neither variable clears the other. Every push the hook **permits** without meeting either condition exits 0 (`scripts/pre-push-gate.sh`). A push the hook *refuses* is a third outcome and not a row below: it never happens, so nothing authorized it. So the gate is chosen by **both** axes, and an installed hook never demotes L2 for a push the hook does not prompt on:

| Push (one the hook permits) | L1 installed | L1 not installed | |------|--------------|------------------| | Protected branch, `ALLOW_PUSH_PROTECTED` unset | **L1 authorizes** — `/dev/tty` confirmation, immune to permission caching. L2 stays required but advisory | **L2 authorizes** — the AskUserQuestion in Phase 0/1 | | History-rewriting push — a **branch**

Read more
Ships withsd0x-dev-flow

Language: English | 繁體中文 | 简体中文 | 日本語 | 한국어 | Español The harness layer for Claude Code. Let the model choose the path. Keep "done" verifiable. Full control plane on Claude Code. Skills-only distribution for Codex CLI and other compatible agents.

Get the whole plugin

Other skills on sd0x-dev-flow.