Commit, push, and optionally open a pull request

SkillDev tools

Use when asked to commit, push a feature branch, and open or update a pull request with gh in one pass. Not for a push with no PR: use commit-push. Not for a PR body alone: use create-pull-request.

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the Commit, push, and optionally open a pull request skill

What this skill tells your AI

The instructions your AI receives, as published by outlinedriven/outline-driven-development in .devin/skills/commit-push-pr/SKILL.md and read by ahel’s review.

Commit the working tree and push the feature branch to origin. In pull-request mode, open or update the pull request with gh; in no-PR mode, stop after the push. Commit authoring belongs to commit. Title and body authoring belongs to create-pull-request. This skill adds branch placement, the PR target resolution when a PR is requested, the push, and the gh call when a PR is requested.

Contract

FieldBound contract
TriggerUser asks to commit and push a feature branch, either with a pull request or without one.
AuthorityHuman-gated: both modes write local commits, at most one local feature branch, and one push to origin/<branch>. Pull-request mode also creates or edits one pull request through gh; no-PR mode creates no pull request. The skill states the mutation set before each remote mutation. Rollback is git reset --hard <prior-HEAD> for new commits, git branch -D <branch> for a branch this skill created, and, in pull-request mode, gh pr close or gh pr edit restoring the prior body for the PR. No remote mutation without the gate.
Side effectLocal commits, at most one local feature branch, and one git push to origin. Pull-request mode also creates or edits one PR on GitHub; no-PR mode does not create or edit a PR. No force push.
DoneNo-PR mode: the branch is pushed with git status --porcelain empty and git rev-list --left-right --count origin/<branch>...HEAD printing 0 0. Pull-request mode: those checks pass and gh pr view --json url,title,state returns the PR as OPEN.

Inputs

  • Mode: optional; defaults to pull-request. Use pull-request to open or update a PR, or no-pull-request to push the feature branch only. This selects whether PR-target resolution, title/body composition, and the PR call occur.
  • Working-tree state (git status, git diff HEAD): required, gathered by the skill.
  • Current branch and recent history (git branch --show-current, git log --oneline -10): required, gathered by the skill.
  • Remote default branch (git rev-parse --abbrev-ref origin/HEAD, then git ls-remote --symref origin HEAD, then gh repo view --json defaultBranchRef --jq '.defaultBranchRef.name' verified against git ls-remote --heads origin <default>): required for branch placement in either mode. When every resolver fails, the skill stops; a default branch is never guessed.
  • Existing PR for the branch (gh pr view --json url,title,state): required only in pull-request mode, gathered by the skill. NO_OPEN_PR when none exists.
  • Remotes (git remote -v) and fork relationship (gh repo view <origin-slug> --json nameWithOwner,parent,defaultBranchRef): required for the PR target in pull-request mode; remotes are still gathered for the push in no-PR mode.
  • gh installed and authenticated (gh auth status): required for pull-request mode. The default-branch fallback may still use gh repo view in either mode.
  • Evidence supplied by the user (URL, image embed, artifact path): optional in pull-request mode, placed in the PR body as given.
  • User decisions: detached HEAD, unpushed local default-branch commits, an ambiguous PR target, and whether to rewrite an existing PR's description.

Procedure

  1. Gather context: run git status, git diff HEAD, git branch --show-current, git log --oneline -10, git remote, and git rev-parse --abbrev-ref origin/HEAD 2>/dev/null || echo DEFAULT_BRANCH_UNRESOLVED. In pull-request mode, also run gh pr view --json url,title,state 2>/dev/null || echo NO_OPEN_PR and gh auth status >/dev/null 2>&1 || echo GH_NOT_AUTHENTICATED. In no-PR mode, omit those PR-only probes. When git remote lists no origin, skip remote default and PR-target resolution: nothing will be pushed and no PR can be filed. Do not classify against a default; if HEAD is detached, still run the step 3 detached-HEAD decision, then commit on the current branch and end at the local-only report. Otherwise, strip the origin/ prefix from the default branch; treat DEFAULT_BRANCH_UNRESOLVED or bare HEAD as unresolved. When unresolved, run git ls-remote --symref origin HEAD and take the branch named by the first ref: refs/heads/<name> line. When that fails too, run gh repo view --json defaultBranchRef --jq '.defaultBranchRef.name' and accept its name only when git ls-remote --heads origin <name> lists it. When every resolver fails, report DEFAULT_BRANCH_UNRESOLVED and stop; never guess main or any other default. Done when: tree state, branch, recent history, and the required default branch and PR state for the selected mode are known, or the skill has stopped.

  2. In pull-request mode, resolve the PR target. The branch always pushes to origin; this step decides only where the PR is filed. Read git remote -v. With an upstream remote, the PR target is upstream. Otherwise run gh repo view <origin-slug> --json nameWithOwner,parent,defaultBranchRef. A non-null parent makes parent.nameWithOwner the target candidate. Ask the user, with the platform blocking question tool (AskUserQuestion in Claude Code, request_user_input in Codex, ask_question in Antigravity, ask_user in Pi), only when no single target is clear: no upstream remote and two or more non-origin candidates, gh's parent disagrees with the upstream remote, or origin shares no merge-base with the target. A fork that is only behind its parent is not ambiguous. When upstream is the target, read the fork owner with gh repo view <origin-slug> --json owner --jq .owner.login. The head is <fork-owner>:<branch> and the base is the target's default branch. gh pr create --head <owner>:<branch> supports user-owned forks only. For an organization-owned origin, report that gh cannot file the PR and stop after the push. With no upstream and no parent, origin is both the push target and the PR target. In no-PR mode, skip this step and record that no PR target is requested. Done when: pull-request mode has a PR target repository, base, and head named, or no-PR mode has recorded that no PR target is requested.

  3. Classify the branch state. When step 1 found no origin, skip default and feature-branch classification: after any detached-HEAD decision, continue directly to step 5 on the current branch and end with the local-only report. Otherwise, done when HEAD is on the branch that will be pushed, or the skill has stopped.

    • Detached HEAD (empty branch name): ask whether to create a feature branch. Use the platform blocking question tool (AskUserQuestion in Claude Code, request_user_input in Codex, ask_question in Antigravity, ask_user in Pi). Fall back to chat only when no blocking tool exists or the call errors. On no, stop. On yes, derive the typed branch name per commit and run git checkout -b <branch>.
    • Default branch, no work (clean tree, nothing unpushed): report nothing to do and stop.
    • Default branch with work: create a feature branch by step 4. Do not ask. A PR from the default branch is not supported here, and no-PR mode also never pushes the default branch.
    • Feature branch: continue at step 5.
  4. Create the feature branch from a fresh default tip. Run git fetch --no-tags origin refs/heads/<default>:refs/remotes/origin/<default> (the explicit refspec materializes the tracking ref even on a single-branch clone), then git log origin/<default>..HEAD --oneline. Empty output: the base is origin/<default>. Non-empty output: show the list and ask whether to carry those commits onto the new branch (base HEAD) or leave them on the local default (base origin/<default>). Never pick silently, because foreign commits in a PR or a no-PR publication cost more than a question. Derive the typed branch name per commit and run git checkout -b <branch> <base>. If checkout refuses because uncommitted changes would be overwritten, run git stash push -u -m "commit-push-pr: pre-branch <branch>", repeat the checkout, then git stash pop. If the fetch fails, report and stop; do not branch from HEAD or from an unverified base. Done when: git branch --show-current prints the new branch and the working tree carries the work.

  5. Author the commits with commit. Its message conventions, atomicity, and staging discipline apply. Do not restate them here. Its default-branch auto-branching is already satisfied by step 4. Done when: commit reports the working tree committed, or reports nothing to commit.

  6. State the push set. Detect a branch new to origin by observing the remote: git ls-remote --heads origin <branch>. Non-empty output means the branch exists; empty output means it is new; a failed call means the remote cannot be observed, so report and stop without pushing. Existing branch: refresh the tracking tip with git fetch --no-tags origin refs/heads/<branch>:refs/remotes/origin/<branch>; when the fetch fails, report and stop without pushing. List git log --oneline origin/<branch>..HEAD. New branch: say so, then list the full commit range git log --oneline origin/<default>..HEAD; origin/<default> comes from the step 4 fetch when this skill created the branch, otherwise materialize it first with git fetch --no-tags origin refs/heads/<default>:refs/remotes/origin/<default>. When no fetched base exists (that fetch failed and origin/<default> is absent), report and stop without pushing; never fall back to an unanchored or truncated listing. If no origin remote exists, report local-only with the commit list and stop. Done when: the push set is stated, or a stop or local-only result is reported.

  7. In pull-request mode, compose the title and body with create-pull-request. Its title rule, sizing table, body order, and diff-does-not-show principle apply. Do not restate them here. Give it the base remote from step 2 so the diff is taken against the right base. Decide evidence before composing: user-supplied evidence goes into the body as ## Demo, ## Screenshots, or ## Evidence by artifact type. When the user asks for evidence but supplied none, ask for the URL, embed, or path. When the change has no observable behavior (internal plumbing, type-only, docs, CI, tests), skip evidence without asking. When the diff changes observable behavior, add a short validation note stating what was exercised and how it behaved, or state plainly why no run was possible. Never label test output as Demo or Screenshots. Do not block the PR on a missing visual artifact. In no-PR mode, skip title and body composition. Done when: pull-request mode has a drafted title and body, or no-PR mode has skipped PR composition.

  8. Preview the remote mutations and wait for approval. In no-PR mode, show the push target and commit list from step 6, then wait for approval of that exact push. In pull-request mode, show the push target and commit list from step 6 with the PR target, base, head, title, and the first two sentences of the body, then wait for approval of that exact mutation set. On no answer or refusal, stop with nothing pushed and no PR created or edited. On approval, re-run the step 6 listing and confirm it still shows the approved commit set; on any difference, re-preview the new set and wait again. Then push with git push -u origin HEAD, never force-push. In no-PR mode, stop after the accepted push and proceed to verification. In pull-request mode, create or edit the PR after the push. Write the body to a temp file and pass it with --body-file, never as an inline --body or through stdin, because wrappers can hand gh an empty body while it still exits 0 and prints a URL:

    # mktemp keeps the body out of shell expansion; the quoted sentinel protects $VAR, backticks, and any literal EOF inside it
    WORK_DIR=$(mktemp -d "${TMPDIR:-/tmp}/odin-pr.XXXXXX")
    BODY_FILE="$WORK_DIR/odin-pr-body.md"
    cat > "$BODY_FILE" <<'__ODIN_PR_BODY_END__'
    <the composed body, verbatim>
    __ODIN_PR_BODY_END__
    

    New PR, same repository: gh pr create --base <default> --title "<TITLE>" --body-file "$BODY_FILE". New PR, upstream target: add --repo <upstream-slug> --base <default> --head <fork-owner>:<branch>. Existing PR: the pushed commits are already on it. Report the URL and ask whether to rewrite the description. On yes, gh pr edit --title "<TITLE>" --body-file "$BODY_FILE", with --repo <upstream-slug> when upstream is the target, because a fork PR is not guaranteed to resolve from the branch alone. Escape ", backticks, $, and \ in the title. Delete $WORK_DIR on every path. Done when: no-PR mode has pushed, or pull-request mode has pushed and the PR is created or edited, or the user declined the description edit, or a stop is reported.

  9. Verify: no-PR mode requires git status --porcelain empty and git rev-list --left-right --count origin/<branch>...HEAD printing 0 0. Pull-request mode additionally requires gh pr view --json url,title,state (with --repo <upstream-slug> when upstream is the target) to return OPEN with the previewed title. Done when: all checks for the selected mode read clean, or the residue is reported.

Failure and recovery

  • gh missing or not authenticated (GH_NOT_AUTHENTICATED at step 1): in pull-request mode, stop before any commit and instruct the user to install gh or run gh auth login; no-PR mode does not require PR authentication.
  • Clean tree with nothing to push: report nothing to do. No mutation occurs.
  • Detached HEAD and the user declines a branch: stop. No commit is created.
  • Stash pop conflicts after branch creation: report the conflict output and the stash ref. Do not resolve them without the user.
  • Fetch failure before branching: report and stop. No branch is created, and there is no fallback to HEAD.
  • Default branch unresolved after origin/HEAD, git ls-remote --symref origin HEAD, and verified gh: report and stop. Never guess a default branch.
  • No fetched base for a new-branch preview (fetch of <default> failed and origin/<default> is absent): report and stop before the push. The commit set is never previewed from HEAD alone or truncated.
  • Tracking-tip refresh fails before an existing-branch preview: report and stop without pushing.
  • Ambiguous PR target and no user answer: in pull-request mode, stop with nothing pushed. The local commits stay for retry once the target is chosen; no-PR mode has no target decision.
  • Organization-owned fork as origin with an upstream target: push, then report that gh --head supports user-owned forks only and give the compare URL for manual creation.
  • No origin remote: report local-only with the commit list. Never add, invent, or guess a remote.
  • Push rejected on a diverged remote branch: report the rejection and the counts from git rev-list --left-right --count origin/<branch>...HEAD. Never force-push.
  • PR creation fails after the push: in pull-request mode, report the exact gh error. The remote branch stays for retry. Never leave a half-created PR unreported.
  • Preview declined: stop with nothing pushed and no PR created or edited. The user may pass focus text for a regenerate.
  • The commit list changed between approval and push: stop and re-preview. Never push a set the human has not seen.
  • Empty PR body after creation: in pull-request mode, re-read gh pr view --json body. If empty, run gh pr edit --body-file "$BODY_FILE" and confirm with a fresh read.
  • Verification residue (uncommitted files, counts other than 0 0, or, in pull-request mode, a PR state other than OPEN): report it. Do not claim done.

Output

No-PR mode produces the committed working tree on the pushed feature branch, one origin push, and a report naming the branch, pushed commit hashes and subjects, and verification. Pull-request mode produces the committed working tree on the pushed feature branch, one origin push, one pull request created or updated on the resolved target, and a report naming the branch, pushed commit hashes and subjects, PR URL, whether it was created or updated, and verification. When no origin remote exists or approval is declined, either mode reports local-only with the commits left unpushed.

Signals

GitHub stars
52
Forks
9
Last commit
Sep 2026
Advanced
Catalog kind
skill
Gateway key
commit-push-pr-2
Source
github.com/outlinedriven/outline-driven-development