gh-stack
Native stacked pull requests through the github/gh-stack extension.
Skill by sd0xdev
Chalk Labs
Need help deploying gh-stack?
Chalk Labs sets up agents like this inside real teams: connected to your tools, tested on your data, with a human in the loop where it matters.
Install
Works with Claude Code, Claude apps, Codex, Cursor, Gemini CLI, GitHub Copilot, VS Code, OpenCode, Goose, Windsurf. Read the repository before you give any tool access to your data.
npx agentmarket install skill/sd0xdev-gh-stack
Asks which of your tools to add it to (Claude Code, Codex, Cursor, VS Code, Windsurf, Gemini CLI, Claude Desktop) and shows the change first. See https://agentmart.dev/cli.
Get the weekly diff
The weekly diff: one email a week with new agent tools, notable updates and grade changes, plus security alerts for tools you follow. Double opt-in, one-click unsubscribe.
About
Use cases: devops
Reviews
Write a reviewNo reviews yet. Installed it? Reviews come from verified installers only. How reviews work
Permissions
What this skill can do and what it asks for, from its SKILL.md. Checked Oct 25, 2026.
- Tools
- No tools found
- Secrets
- None asked for
- Install scripts
- None found
Network hosts it talks to 1
codeload.github.com· found in install command
Files it touches
~/.claude/skills/gh-stack
Tools it pre-approves 11
From allowed-tools in SKILL.md: the agent may use these without asking you each time.
Bash(gh:*),Bash(git:*),Bash(bash:*),Bash(/bin/bash:*),Bash(mktemp:*),Bash(rm:*),Read,Write,Grep,Glob,AskUserQuestion
What's inside
- Skill name
gh-stack- Description
- Native stacked pull requests through the github/gh-stack extension. Detects the extension, installs it only after the user approves, resolves the branch chain, and executes `gh stack link` / `gh stack push` / `gh stack submit --auto` under a per-use AskUserQuestion gate. Use when: opening or refreshing a native stack of PRs, linking existing PRs into a stack on GitHub, pushing a stack's branches, or when /create-pr --stack delegates the native path here. Not for: a single PR (use /create-pr), pushing one branch (use /push-ci), merging a chain (use /epic-merge), or any history rewrite — `gh stack rebase` / `sync` / `modify` stay the user's to run. Output: stack table + per-command verdict + attribution verify.
- License
- MIT
- Allowed tools
- Bash(gh:*),, Bash(git:*),, Bash(bash:*),, Bash(/bin/bash:*),, Bash(mktemp:*),, Bash(rm:*),, Read,, Write,, Grep,, Glob,, AskUserQuestion
- Instructions
- 718 lines in SKILL.md · view on GitHub
Before installing
- Pre-approves some shell commands
- Instructions read files in your home folder (~/.ssh, ~/.aws…)
Agent Skills spec issues
- body: 718 lines (spec recommends under 500)
- files: referenced but missing: scripts/commit-msg-guard.sh
Files (1)
- SKILL.md
SKILL.md (first lines)
# GH Stack — Native Stacked PRs
**Read first**: `@skills/push-ci/references/authorization-contract.md` before any `gh stack` operation that pushes — § Push safety for the credential and the unshared attestation, § Efficacy Boundary for what the per-use approval authorizes. If that Read fails, stop and report it; run no pushing operation.
Wraps the `github/gh-stack` extension so a branch chain becomes a real **Stack** on GitHub — one PR per layer, chained bases, per-layer diff view, linked merges — instead of the hand-built chained-base PR set `/create-pr --stack` produces on its own.
## Authorization
```
⚠️ This skill is one of the four workflows Anchor Register #4 enumerates (@rules/discretion.md).
⚠️ It may execute exactly three subcommands of the extension — `gh stack link`, `gh stack push`,
`gh stack submit --auto` — and nothing else from it, `gh stack view` included: `view` syncs PR
metadata and writes it back into `.git/gh-stack`, so it is not a read. What this skill needs to
read, it reads without the extension (the first row of the table below).
⚠️ All three push, and they do not push the same way. Read from the extension's source at v0.1.1
(§ Force form): `link` runs a plain `git push --atomic`; `push` and `submit --auto` run a
per-branch, value-bearing `git push --force-with-lease=refs/heads/<b>:<sha>`. The approval
names the form the chosen subcommand uses.
⚠️ Every `gh stack` invocation REQUIRES explicit per-use user approval via AskUserQuestion
— no flag, no earlier approval and no delegating skill substitutes for it.
⚠️ Every other subcommand is printed for the user to run, never executed.
```
| Operation | Executor | Authorization |
|-----------|----------|---------------|
| `gh --version`, `gh auth status` (exit status only), `gh extension list`, `gh pr list --json`, `gh pr view --json`, `gh api 'repos/{owner}/{repo}/stacks?pull_request=<n>'` (GET), `git rev-parse`, `git merge-base`, and the extension's tracking file `<git-dir>/gh-stack` read with the Read tool | this skill | read-only |
| `gh extension install github/gh-stack` | this skill | Phase 1 AskUserQuestion — **never** installed silently |
| `gh stack link`, `gh stack push`, `gh stack submit --auto` | this skill | Phase 3 per-use AskUserQuestion naming the push form and the branches |
| `gh stack view`, `gh stack rebase`, `gh stack sync`, `gh stack modify`, `gh stack merge`, `gh stack unstack`, `gh stack init`, `gh stack add`, `gh stack checkout` | **user only** — printed, never run | outside the grant |
| `gh pr edit` for an attribution remediation | this skill | `/create-pr` § 7b contract (CLAUDE.md rule 3, Anchor) |
| `git push` by any other route | `/push-ci` | its own workflow |
**Why that split, rather than "whatever the extension offers".** The three granted subcommands move
**remote refs and PR state**; the ungranted ones rewrite **local history** (`rebase`, `sync`,
`modify`), merge PRs (`merge`), or need a TUI no agent can drive (`modify`, `checkout` without an argument, and `submit` without `--auto`). `sync` is the one that reads like a convenience and is not: it fetches, cascade-rebases,
force-pushes and prunes in one call, so approving "a sync" approves a history rewrite nobody has
seen. It stays the user's. `view` is the one that reads like a query and is not: both its `--json`
and its default form sync PR metadata from GitHub and then call `SaveNonBlocking`, which rewrites
`.git/gh-stack` (`cmd/view.go`, v0.1.1). A read that persists state is a mutation of the
extension's own bookkeeping, and nothing in this skill needs it — the file can be read directly,
and the remote Stack has a REST endpoint of its own.
**`gh stack submit` is never run without `--auto`.** Interactively it opens a full-screen editor;
an agent invoking it hangs the session holding an approved force push. Readiness is the same for **both** publishing forms — `gh stack submit --auto` and
`gh stack link` alike create new PRs as **drafts** unless `--open` is passed, and `--open` is carried into the Phase 3 approval — a caller may propose it, this skill does not
execute it unless that approval affirms readiness.
### Push safety — the obligation this skill carries itself
Every granted subcommand pushes, two of them with force, and the `pre-push` hook is **opt-in**
(@rules/git-workflow.md § Push safety). So:
| Obligation | How this skill meets it |
|------------|-------------------------|
| The **unshared attestation** — *is anybody else working on the branches this push rewrites* | For `gh stack push` and `gh stack submit --auto`, Phase 3 asks it **by name and before the force approval**, listing the branches, and refuses the invocation when the answer is not the attestation. An installed hook may ask again over `/dev/tty`; that is defence in depth, never a reason to skip the question, because where the hook is absent nothing else asks. `gh stack link` rewrites nothing: its push carries no force, so a branch that diverged on the remote is refused by git itself and the run stops there (§ Force form) |
| `ALLOW_PUSH_PROTECTED` and `ALLOW_FORCE_UNSHARED` are developer-set only | Never set by this skill, and **cleared on every invocation it executes** — a value exported earlier in the shell answers the hook's question without anybody being asked now |
| `ALLOW_FORCE_WITH_LEASE` | Set **only** on the single approved `gh stack push` or `gh stack submit --auto` line, in the same phase that obtained the force-form approval — the same shape `/push-ci` uses — and **never on `gh stack link`**, which does not force. Without it the hook refuses the force-form push outright, which is a refusal, not an authorization of anything |
| Protected branches | A protected branch (`main`/`master`/`develop`/`release/*`, plus the project's additions — Phase 2's resolver) may be the stack's **base** and may never appear as a stack **layer**. Phase 2 hard-aborts if one does |
**Why this skill is model-invocable when `/push-ci` and `/epic-merge` are not.** Both set
`disable-model-invocation: true`; this one deliberately does not, because `/create-pr --stack`
reaches it through the `Skill` tool and that flag would block the delegation the routing depends on.
The trade is stated rather than silent, and it is a trade rather than an equivalence: the outer stop
those two get from the flag is **not available here**, and what stands in its place is Phase 3's two
questions — which § Efficacy Boundary's caching weakness still reaches, where the flag would not
have. What Phase 3 does hold absolutely is that no caller, flag or earlier turn substitutes for the
question being asked.
### Force form — read from the source, bound to v0.1.1
`ALLOW_FORCE_WITH_LEASE=1` on the executed line silences one refusal in `pre-push-gate.sh`: the
non-fast-forward `exit 1`. That gate's own comment records why the bypass is safe for the other two
workflows — *"Git hooks cannot distinguish `--force` from `--force-with-lease` (same ref data).
Callers … set `ALLOW_FORCE_WITH_LEASE=1` only when `--force-with-lease` is explicitly requested.
**Bare `--force` enforcement is at the caller level.**"* — and **here the caller does not compose
the flag**: the extension does. The push form is therefore a claim about somebody else's code, and
it is stated from that code, not from its README (which describes `link` as if it forced, and does
not):
| Subcommand | The push it issues — `github/gh-stack` v0.1.1 | Source |
|------------|-----------------------------------------------|--------|
| `gh stack link` | `git push <remote> --atomic refs/heads/<b>:refs/heads/<b> …` — **no force**, only the operands that name local branches | `cmd/link.go` `pushBranchArgs` → `git.Push(remote, branches, false, true)` |
| `gh stack push` | `git push <remote> --force-with-lease=refs/heads/<b>:<sha> … refs/heads/<b>:refs/heads/<b> …` — one lease per branch, `<sha>` read from `refs/remotes/<remote>/<b>`, and an empty value (`refs/heads/<b>:`, *must not exist*) for a branch the remote does not have yet. Not atomic | `cmd/push.go` → `internal/git/gitops.go` `Push` with force=true, atomic=false |
| `gh stack submit --auto` | the same form, one branch per `git push` | `cmd/submit.go` → the same `Push` |
Three consequences, each of them contract:
1. **That version issues no bare `--force`, and every refspec is fully qualified** — a branch named
`+main` stays a ref name and never becomes a force modifier. This is what the grant covers.
2. **The lease is not a sharedness check.** Both forcing subcommands call `FetchBranches`
immediately before the push, so `<sha>` is whatever the remote held a moment earlier: the lease
stops a race between that fetch and the push, and nothing else. A collaborator's commits that
were already on the remote pass it and are overwritten. That is why Phase 3 asks the unshared
attestation for these two subcommands, and why a lease being present is never a reason to skip it.
3. **The reading is bound to the version it was read from.** Phase 0 records the installed version
(`GH_STACK_VERSION`). On anything other than `v0.1.1` the push form is **unverified**, and an
unverified push form cannot be approved: **every mutating run stops before Phase 3** and reports
the version it found. A trace is no substitute — `GIT_TRACE=1` records a push that has already
happened, so a bare `--force` it revealed would already be on the remote; and `GH_DEBUG` logs API
traffic, not the subprocess argv. The way back is a maintainer reading that version's sources —
the files the table above cites — and moving the pinned version here and in
`test/skills/gh-stack.test.js` together. A bare `--force` or an unqualified refspec found in
**any** version is outside the grant: this skill stops using that subcommand and reports to the
maintainer — no per-use approval can cover it.
**The push is not the whole of what these subcommands change, and the approval has to cover all of
it.** Read from the same sources, the complete mutation surface of the three at v0.1.1:
| Subcommand | Changes beyond the push |
|------------|-------------------------|
| `gh stack link` | creates a PR for every branch operand without one (draft unless `--open`); **retargets the base** of an existing PR that is not on the expected one; marks existing drafts ready under `--open`; creates the Stack or appends to the one a PR already belongs to. It *refuses* merged, closed, queued and auto-merge PRs rather than changing them |
| `gh stack push` | fetches the stack's branches, then writes `.git/gh-stack`. Its own modify guard refuses only a modify that is `applying` or in `conflict` — a `pending_submit` state passes it, and so does a state file it cannot read (`internal/modify/state.go` `CheckStateGuard`); `push` does not unstack anything, so that state is no hazard to it |
| `gh stack submit --auto` | **resolves a pending `gh stack modify` first — non-interactively, by unstacking the Stack recorded in `<git-dir>/gh-stack-modify-state`, which need not be the one being submitted**; fetches; per layer pushes, then **disables auto-merge** on an existing PR, retargets its base when the stack is not yet on GitHub, marks drafts ready under `--open`, or creates the PR with generated text; creates or extends the Stack; writes `.git/gh-stack` |
Every row is a state change the approval names (Phase 3), except the pending-modify resolution,
which this skill refuses rather than approves (Phase 2): deleting a Stack is outside the grant. The
extension only **warns** when a base retarget, an auto-merge change or a ready-marking fails, and
still exits `0` — which is why Phase 4 reads every one of them back instead of trusting the exit.From sd0xdev/sd0x-harness, MIT.
More from sd0xdev/sd0x-harness
- 0.713score
debug
sd0xdev
Interactive debugging workflow with hypothesis-driven probe loop.
SkillAFreshness: A★ 194Claude Code - 0.713score
contract-decode
sd0xdev
EVM contract error and calldata decoder.
SkillAFreshness: A★ 194Claude Code - 0.713score
codex-brainstorm
sd0xdev
Adversarial brainstorming via Claude+Codex debate.
SkillAFreshness: A★ 194Claude Code +9 - 0.713score
best-practices
sd0xdev
Industry best practices conformance audit with mandatory adversarial debate.
SkillAFreshness: A★ 194Claude Code - 0.713score
codex-cli-review
sd0xdev
Code review via Codex CLI with full disk access.
SkillAFreshness: A★ 194Claude Code +9 - 0.713score
codex-explain
sd0xdev
Explain complex code via Codex exec.
SkillAFreshness: A★ 194Claude Code +9 - 0.713score
ask
sd0xdev
Context-aware Q&A with auto context gathering.
SkillAFreshness: A★ 194Claude Code +9 - 0.713score
claude-health
sd0xdev
Claude Code config health check + plugin sync.
SkillAFreshness: A★ 194Claude Code +9 - 0.713score
code-explore
sd0xdev
Pure Claude code investigation.
SkillAFreshness: A★ 194Claude Code +9 - 0.713score
code-investigate
sd0xdev
Dual-perspective code investigation.
SkillAFreshness: A★ 194Claude Code +9 - 0.713score
codex-architect
sd0xdev
Codex architecture consulting.
SkillAFreshness: A★ 194Claude Code +9 - 0.713score
deep-explore
sd0xdev
Multi-wave parallel code exploration orchestrator.
SkillAFreshness: A★ 194Claude Code +9
Signals
- Stars
- 194
- Forks
- 26
- Open issues
- 5
- Last commit (this folder)
- Sep 26, 2026 (1 month ago)
- Last release
- Oct 6, 2026 (26 days ago)
From GitHub (fetched Oct 24, 2026).
Details
- License
- MIT
- Repository
- github.com/sd0xdev/sd0x-harness
- First seen
- Oct 24, 2026
- Last checked
- Oct 24, 2026
- Source
- GitHubgithub-graphql
More from sd0xdev
- 0.713score
code-investigate
sd0xdev
Dual-perspective code investigation.
SkillAFreshness: A★ 194Claude Code +9 - 0.713score
code-explore
sd0xdev
Pure Claude code investigation.
SkillAFreshness: A★ 194Claude Code +9 - 0.713score
ask
sd0xdev
Context-aware Q&A with auto context gathering.
SkillAFreshness: A★ 194Claude Code +9 - 0.713score
best-practices
sd0xdev
Industry best practices conformance audit with mandatory adversarial debate.
SkillAFreshness: A★ 194Claude Code - 0.713score
claude-health
sd0xdev
Claude Code config health check + plugin sync.
SkillAFreshness: A★ 194Claude Code +9 - 0.713score
codex-architect
sd0xdev
Codex architecture consulting.
SkillAFreshness: A★ 194Claude Code +9
Done for you
Rather not set it up yourself?
Chalk Labs scopes, builds and runs agents for your team: the task audit, the eval harness, the integrations and the weekly ops report. Fixed scope, priced before we start.
See how we build AI agents →Read the case studies
Chalk Labs is a two-founder AI and growth studio. We build AI agents, CRMs and MVPs for Web3 and AI companies, and have shipped 50+ product and token launches.
- launches shipped
- 50+
- launches shipped
- media placements
- 500+
- media placements
- founders on every account
- 2
- founders on every account
Press we've placed: Forbes · CoinDesk · Decrypt · Cointelegraph · The Block
Rahil Jain, Co-founder, Chalk Labs. Ex-Goldman Sachs engineer. You talk to the person who builds it.
Get gh-stack running in your business
Tell us what you want to automate. A founder replies within one working day with a plan, not a pitch deck.
Cite this page
gh-stack skill. Agent Mart. https://agentmart.dev/skills/sd0xdev-gh-stack (last updated 2026-10-24)