GH Stack — Native Stacked PRs
SkillDev toolsLets your agent use the gh stack skill to turn a chain of branches into stacked pull requests on GitHub, one PR per layer.
Available today. Use it from your connected AI after setup.
No other account needed.
Add ahel to your AI once: Claude, ChatGPT, Cursor, Claude Code or Codex. Then ask it to use this.
Then ask your AI: use the GH Stack — Native Stacked PRs skill
About this skill
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 refresh
What this skill tells your AI
The instructions your AI receives, as published by sd0xdev/sd0x-harness in skills/gh-stack/SKILL.md and read by ahel’s review.
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:
- That version issues no bare
--force, and every refspec is fully qualified — a branch named+mainstays a ref name and never becomes a force modifier. This is what the grant covers. - The lease is not a sharedness check. Both forcing subcommands call
FetchBranchesimmediately 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. - The reading is bound to the version it was read from. Phase 0 records the installed version
(
GH_STACK_VERSION). On anything other thanv0.1.1the 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=1records a push that has already happened, so a bare--forceit revealed would already be on the remote; andGH_DEBUGlogs 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 intest/skills/gh-stack.test.jstogether. A bare--forceor 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.
PUSH_GATE detection (Phase 0) reports whether an executable pre-push hook references
pre-push-gate.sh. Reference is not invocation: it informs how the plan describes the
credential and never selects one. If the approval is given and no /dev/tty prompt appears, that
in-session approval was the only approval.
Input
/gh-stack [--base <trunk>] [<branch>...] # explicit chain, bottom first; resolve + report only
/gh-stack --link [--base <trunk>] <branch-or-pr>... # a chain YOU name (bottom first): push it, create/chain the PRs, create the Stack
/gh-stack --submit # the stack the extension already tracks locally (.git/gh-stack); takes no operands
/gh-stack --push # push the tracked stack's branches, no PR changes
/gh-stack --open # with --link or --submit: mark new and existing PRs ready for review
/gh-stack --install # detection + install offer only; already installed → report and stop
--link and --submit are not two spellings of one operation, and picking the wrong one is how
an approved force push acts on branches nobody named. gh stack submit takes no operands: it
publishes the stack gh stack tracks locally in .git/gh-stack, which is built by gh stack init
/ add — both outside this skill's grant. gh stack link is the one that takes a chain, and the
extension documents it for exactly this case ("users who manage branches with other tools locally"):
it pushes the named branches, reuses or creates each PR with the correct base chaining, and creates
or extends the Stack. So:
| The chain came from | Form | Why |
|---|---|---|
An explicit argument — the user's, or /create-pr --stack's validated chain | --link | operands exist, no local tracking is assumed or written |
The extension's own local tracking — exactly one stack in .git/gh-stack containing the current branch | --submit | there is nothing to name; the tracked stack is the operand |
| Neither | STOP (Phase 2) | — |
A --submit run therefore never carries branch arguments, and its approval names every layer of
the tracked stack Phase 2 selects — the stack submit itself selects (cmd/submit.go:
FindAllStacksForBranch) — as an upper bound: the extension skips merged and queued layers at
run time, so it may push fewer and never others (§ Phase 2, Tracked stack). Not a list this skill
assembled, and not a prediction of which layers will move. --push is the same shape:
gh stack push pushes the tracked stack's branches and takes no operands either, so branch
arguments with --push are a parameter error — accepting them would let an attestation and a force
approval name branches while the execution moved whatever the extension happens to track.
Any run without --install and without a mutating flag is read-only: detect, resolve, report — including the
/gh-stack <layer>… form /create-pr --stack delegates in dry-run, which carries arguments and no
flag. Nothing mutating happens without both a flag that names the operation and an approval:
Phase 3's for the three gh stack subcommands, Phase 1's own for --install — which is mutating
too (it writes an executable to disk) and is the one mutation Phase 3 does not gate.
Workflow
Phase 0 detect → Phase 1 install (approval) → Phase 2 resolve chain → Phase 3 approve + execute → Phase 4 verify + report
Phase 0 — Environment detection (read-only)
Step 0a/0b first: the interpreter and the transport, before anything is read. This skill
executes pushes through the extension, two of its subcommands with force-with-lease, so it carries the same two refusals
/push-ci § Phase 0 and /epic-merge carry, for the same measured reason
(docs/features/push-gate-optin/4-implementation.md § 4): a non-interactive bash sources
$BASH_ENV before line 1 of any fence below (zsh does the same with $ENV under sh emulation), and
a sourced file may define a function named /usr/bin/env — measured, the word resolved to the
function and the child never ran. Under that, the -u ALLOW_PUSH_PROTECTED -u ALLOW_FORCE_UNSHARED
clearing this skill promises would be whatever that function chose to do, and both hook prompts
would be silently skipped. Transport variables are refused rather than cleared for the reason
/push-ci § 0b gives: clearing one moves the push to a destination the plan did not describe.
# No command word in this block by design: `[[ ]]` is a keyword the parser resolves, so a function
# cannot outrank it; the rest is assignment and expansion. Set-ness, not emptiness (an exported
# empty value is still a file the parent named). Names are printed, never values.
SHELL_STARTUP_INHERITED=
[[ -n "${BASH_ENV+set}" ]] && SHELL_STARTUP_INHERITED=BASH_ENV
[[ -n "${ENV+set}" ]] && SHELL_STARTUP_INHERITED="${SHELL_STARTUP_INHERITED:+${SHELL_STARTUP_INHERITED}, }ENV"
if [[ -n "$SHELL_STARTUP_INHERITED" ]]; then
# Name the detected variable through `echo`, not through the `${var:?word}` word: measured, zsh
# (this platform's default shell) does NOT parameter-expand that word, so the refusal would read
# a literal `${SHELL_STARTUP_INHERITED}` and tell the operator to unset something it never named.
# Both shells still refuse with rc=1 — only the message degrades, which is why the transport
# block below already echoes its own names.
echo "⛔ shell startup file variables set in this environment: ${SHELL_STARTUP_INHERITED}" >&2
# No apostrophe anywhere in the word below: inside `${var:?word}` bash reads one as an opening
# quote even within double quotes, and that is a PARSE error — it would take the whole fence down
# on every run, refusing and ordinary alike (measured, `/push-ci` § Phase 0 step 0a).
SD0X_GH_STACK_REFUSED=
: "${SD0X_GH_STACK_REFUSED:?refusing — ${SHELL_STARTUP_INHERITED} is set in this environment.
That startup file is sourced before line 1 of every fence below and can redefine the commands
they run, the absolute /usr/bin/env prefix included (measured). The bypass variables this skill
promises to clear would then be cleared by whatever it chose. Unset it and re-run. Nothing is
detected, planned, installed or pushed.}"
fi
# `gh`'s own destination variables sit beside git's here, because this skill's transport IS gh:
# `GH_REPO` retargets every `gh` call at another repository, so an approval naming this repo's
# branches would drive stack and PR creation somewhere else entirely; `GH_HOST` and `GH_CONFIG_DIR`
# move the host and the credentials the same way.
TRANSPORT_PRESENT=
[[ -n "${GH_REPO+set}" ]] && TRANSPORT_PRESENT=GH_REPO
[[ -n "${GH_HOST+set}" ]] && TRANSPORT_PRESENT="${TRANSPORT_PRESENT:+${TRANSPORT_PRESENT}, }GH_HOST"
[[ -n "${GH_CONFIG_DIR+set}" ]] && TRANSPORT_PRESENT="${TRANSPORT_PRESENT:+${TRANSPORT_PRESENT}, }GH_CONFIG_DIR"
[[ -n "${GIT_SSH_COMMAND+set}" ]] && TRANSPORT_PRESENT="${TRANSPORT_PRESENT:+${TRANSPORT_PRESENT}, }GIT_SSH_COMMAND"
[[ -n "${GIT_SSH+set}" ]] && TRANSPORT_PRESENT="${TRANSPORT_PRESENT:+${TRANSPORT_PRESENT}, }GIT_SSH"
[[ -n "${GIT_PROXY_COMMAND+set}" ]] && TRANSPORT_PRESENT="${TRANSPORT_PRESENT:+${TRANSPORT_PRESENT}, }GIT_PROXY_COMMAND"
[[ -n "${GIT_SSH_VARIANT+set}" ]] && TRANSPORT_PRESENT="${TRANSPORT_PRESENT:+${TRANSPORT_PRESENT}, }GIT_SSH_VARIANT"
if [[ -n "$TRANSPORT_PRESENT" ]]; then
echo "⛔ transport variables set in this environment: ${TRANSPORT_PRESENT}" >&2
echo " Each one decides where a push lands, so neither honouring nor clearing them lets this" >&2
echo " run describe the destination the stack would reach." >&2
echo " ssh names (GIT_SSH*, GIT_PROXY_COMMAND): move the setting to ~/.ssh/config or" >&2
echo " 'git config core.sshCommand'. gh names (GH_REPO, GH_HOST, GH_CONFIG_DIR): unset it and" >&2
echo " run from a checkout of the repository you mean. Then re-run." >&2
SD0X_GH_STACK_REFUSED=
: "${SD0X_GH_STACK_REFUSED:?refusing — transport variables set in this environment}"
fi
Then the readings — each one behind the same prefix the Phase 3 execution carries, written out literally:
/usr/bin/env -u BASH_ENV -u ENV -u GIT_EXEC_PATH -u GIT_DIR -u GIT_WORK_TREE -u GIT_COMMON_DIR -u GIT_INDEX_FILE -u GIT_OBJECT_DIRECTORY -u GIT_ALTERNATE_OBJECT_DIRECTORIES -u GIT_NAMESPACE -u GIT_CEILING_DIRECTORIES -u GIT_GLOB_PATHSPECS -u GIT_ICASE_PATHSPECS -u GIT_NOGLOB_PATHSPECS -u GIT_LITERAL_PATHSPECS -u GIT_CONFIG -u GIT_CONFIG_PARAMETERS -u GIT_CONFIG_COUNT -u GIT_CONFIG_NOSYSTEM -u GIT_CONFIG_GLOBAL -u GIT_CONFIG_SYSTEM -u GIT_IMPLICIT_WORK_TREE -u GIT_GRAFT_FILE -u GIT_SHALLOW_FILE -u GIT_PREFIX -u GIT_REPLACE_REF_BASE -u GIT_EXTERNAL_DIFF -u GIT_SSH_COMMAND -u GIT_SSH -u GIT_PROXY_COMMAND -u GIT_SSH_VARIANT GIT_GRAFT_FILE=/dev/null GIT_NO_REPLACE_OBJECTS=1 gh --version
/usr/bin/env -u BASH_ENV -u ENV -u GIT_EXEC_PATH -u GIT_DIR -u GIT_WORK_TREE -u GIT_COMMON_DIR -u GIT_INDEX_FILE -u GIT_OBJECT_DIRECTORY -u GIT_ALTERNATE_OBJECT_DIRECTORIES -u GIT_NAMESPACE -u GIT_CEILING_DIRECTORIES -u GIT_GLOB_PATHSPECS -u GIT_ICASE_PATHSPECS -u GIT_NOGLOB_PATHSPECS -u GIT_LITERAL_PATHSPECS -u GIT_CONFIG -u GIT_CONFIG_PARAMETERS -u GIT_CONFIG_COUNT -u GIT_CONFIG_NOSYSTEM -u GIT_CONFIG_GLOBAL -u GIT_CONFIG_SYSTEM -u GIT_IMPLICIT_WORK_TREE -u GIT_GRAFT_FILE -u GIT_SHALLOW_FILE -u GIT_PREFIX -u GIT_REPLACE_REF_BASE -u GIT_EXTERNAL_DIFF -u GIT_SSH_COMMAND -u GIT_SSH -u GIT_PROXY_COMMAND -u GIT_SSH_VARIANT GIT_GRAFT_FILE=/dev/null GIT_NO_REPLACE_OBJECTS=1 gh extension list
/usr/bin/env -u BASH_ENV -u ENV -u GIT_EXEC_PATH -u GIT_DIR -u GIT_WORK_TREE -u GIT_COMMON_DIR -u GIT_INDEX_FILE -u GIT_OBJECT_DIRECTORY -u GIT_ALTERNATE_OBJECT_DIRECTORIES -u GIT_NAMESPACE -u GIT_CEILING_DIRECTORIES -u GIT_GLOB_PATHSPECS -u GIT_ICASE_PATHSPECS -u GIT_NOGLOB_PATHSPECS -u GIT_LITERAL_PATHSPECS -u GIT_CONFIG -u GIT_CONFIG_PARAMETERS -u GIT_CONFIG_COUNT -u GIT_CONFIG_NOSYSTEM -u GIT_CONFIG_GLOBAL -u GIT_CONFIG_SYSTEM -u GIT_IMPLICIT_WORK_TREE -u GIT_GRAFT_FILE -u GIT_SHALLOW_FILE -u GIT_PREFIX -u GIT_REPLACE_REF_BASE -u GIT_EXTERNAL_DIFF -u GIT_SSH_COMMAND -u GIT_SSH -u GIT_PROXY_COMMAND -u GIT_SSH_VARIANT GIT_GRAFT_FILE=/dev/null GIT_NO_REPLACE_OBJECTS=1 git rev-parse --show-toplevel
/usr/bin/env -u BASH_ENV -u ENV -u GIT_EXEC_PATH -u GIT_DIR -u GIT_WORK_TREE -u GIT_COMMON_DIR -u GIT_INDEX_FILE -u GIT_OBJECT_DIRECTORY -u GIT_ALTERNATE_OBJECT_DIRECTORIES -u GIT_NAMESPACE -u GIT_CEILING_DIRECTORIES -u GIT_GLOB_PATHSPECS -u GIT_ICASE_PATHSPECS -u GIT_NOGLOB_PATHSPECS -u GIT_LITERAL_PATHSPECS -u GIT_CONFIG -u GIT_CONFIG_PARAMETERS -u GIT_CONFIG_COUNT -u GIT_CONFIG_NOSYSTEM -u GIT_CONFIG_GLOBAL -u GIT_CONFIG_SYSTEM -u GIT_IMPLICIT_WORK_TREE -u GIT_GRAFT_FILE -u GIT_SHALLOW_FILE -u GIT_PREFIX -u GIT_REPLACE_REF_BASE -u GIT_EXTERNAL_DIFF -u GIT_SSH_COMMAND -u GIT_SSH -u GIT_PROXY_COMMAND -u GIT_SSH_VARIANT GIT_GRAFT_FILE=/dev/null GIT_NO_REPLACE_OBJECTS=1 git rev-parse --abbrev-ref HEAD
/usr/bin/env -u BASH_ENV -u ENV -u GIT_EXEC_PATH -u GIT_DIR -u GIT_WORK_TREE -u GIT_COMMON_DIR -u GIT_INDEX_FILE -u GIT_OBJECT_DIRECTORY -u GIT_ALTERNATE_OBJECT_DIRECTORIES -u GIT_NAMESPACE -u GIT_CEILING_DIRECTORIES -u GIT_GLOB_PATHSPECS -u GIT_ICASE_PATHSPECS -u GIT_NOGLOB_PATHSPECS -u GIT_LITERAL_PATHSPECS -u GIT_CONFIG -u GIT_CONFIG_PARAMETERS -u GIT_CONFIG_COUNT -u GIT_CONFIG_NOSYSTEM -u GIT_CONFIG_GLOBAL -u GIT_CONFIG_SYSTEM -u GIT_IMPLICIT_WORK_TREE -u GIT_GRAFT_FILE -u GIT_SHALLOW_FILE -u GIT_PREFIX -u GIT_REPLACE_REF_BASE -u GIT_EXTERNAL_DIFF -u GIT_SSH_COMMAND -u GIT_SSH -u GIT_PROXY_COMMAND -u GIT_SSH_VARIANT GIT_GRAFT_FILE=/dev/null GIT_NO_REPLACE_OBJECTS=1 gh auth status >/dev/null 2>&1 && echo GH_AUTH=ok || echo GH_AUTH=failed
/usr/bin/env -u BASH_ENV -u ENV -u GIT_EXEC_PATH -u GIT_DIR -u GIT_WORK_TREE -u GIT_COMMON_DIR -u GIT_INDEX_FILE -u GIT_OBJECT_DIRECTORY -u GIT_ALTERNATE_OBJECT_DIRECTORIES -u GIT_NAMESPACE -u GIT_CEILING_DIRECTORIES -u GIT_GLOB_PATHSPECS -u GIT_ICASE_PATHSPECS -u GIT_NOGLOB_PATHSPECS -u GIT_LITERAL_PATHSPECS -u GIT_CONFIG -u GIT_CONFIG_PARAMETERS -u GIT_CONFIG_COUNT -u GIT_CONFIG_NOSYSTEM -u GIT_CONFIG_GLOBAL -u GIT_CONFIG_SYSTEM -u GIT_IMPLICIT_WORK_TREE -u GIT_GRAFT_FILE -u GIT_SHALLOW_FILE -u GIT_PREFIX -u GIT_REPLACE_REF_BASE -u GIT_EXTERNAL_DIFF -u GIT_SSH_COMMAND -u GIT_SSH -u GIT_PROXY_COMMAND -u GIT_SSH_VARIANT GIT_GRAFT_FILE=/dev/null GIT_NO_REPLACE_OBJECTS=1 /bin/bash -c 'h=$(git rev-parse --git-path hooks/pre-push); [ -x "$h" ] && grep -q pre-push-gate.sh "$h" && echo referenced || echo absent'
Shortened here. Read the whole file on GitHub.
Signals
- GitHub stars
- 190
- Forks
- 26
- Last commit
- Sep 2026
- Hacker News mentions
- 1
Advanced
- Catalog kind
- skill
- Key
gh-stack-sd0xdev- Source
- github.com/sd0xdev/sd0x-harness