GH Stack — Native Stacked PRs

SkillDev tools

Lets 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.

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.
OperationExecutorAuthorization
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 toolthis skillread-only
gh extension install github/gh-stackthis skillPhase 1 AskUserQuestion — never installed silently
gh stack link, gh stack push, gh stack submit --autothis skillPhase 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 checkoutuser only — printed, never runoutside the grant
gh pr edit for an attribution remediationthis skill/create-pr § 7b contract (CLAUDE.md rule 3, Anchor)
git push by any other route/push-ciits 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:

ObligationHow this skill meets it
The unshared attestation — is anybody else working on the branches this push rewritesFor 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 onlyNever 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_LEASESet 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 branchesA 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):

SubcommandThe push it issues — github/gh-stack v0.1.1Source
gh stack linkgit push <remote> --atomic refs/heads/<b>:refs/heads/<b> … — no force, only the operands that name local branchescmd/link.go pushBranchArgs → git.Push(remote, branches, false, true)
gh stack pushgit 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 atomiccmd/push.go → internal/git/gitops.go Push with force=true, atomic=false
gh stack submit --autothe same form, one branch per git pushcmd/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:

SubcommandChanges beyond the push
gh stack linkcreates 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 pushfetches 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 --autoresolves 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 fromFormWhy
An explicit argument — the user's, or /create-pr --stack's validated chain--linkoperands 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--submitthere is nothing to name; the tracked stack is the operand
NeitherSTOP (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