Wednesday Git
wednesday-solutions/ai-agent-skills
Unified Git workflow. An agent skill from wednesday-solutions/ai-agent-skills.
Smart partial rebase for squash-merge repositories. An agent skill from sd0xdev/sd0x-harness.
$ npx skills add sd0xdev/sd0x-harness --skill smart-rebase -a claude-codeProject install by default; add -g for ~/.claude/skills/.
$ gh skill install sd0xdev/sd0x-harness smart-rebase --agent claude-codeProject scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).
$ git clone --depth 1 https://github.com/sd0xdev/sd0x-harness.git skills-src && mkdir -p .claude/skills && cp -r skills-src/skills/smart-rebase .claude/skills/smart-rebase && rm -rf skills-srcUse ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.
Claude Code skills documentation · loads skills from .claude/skills/
Install the "smart-rebase" agent skill from https://github.com/sd0xdev/sd0x-harness/tree/main/skills/smart-rebase into .claude/skills/smart-rebase/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "smart-rebase", then confirm the skill loads.Claude Code copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$skill-installer install https://github.com/sd0xdev/sd0x-harness/tree/main/skills/smart-rebaseType this inside Codex. $skill-installer <name> installs a curated skill from openai/skills. The installer writes to $CODEX_HOME/skills (default ~/.codex/skills). Restart Codex if the skill does not show up.
$ npx skills add sd0xdev/sd0x-harness --skill smart-rebase -a codexProject install goes to .agents/skills/; add -g for ~/.codex/skills/.
$ gh skill install sd0xdev/sd0x-harness smart-rebase --agent codexProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/sd0xdev/sd0x-harness.git skills-src && mkdir -p .agents/skills && cp -r skills-src/skills/smart-rebase .agents/skills/smart-rebase && rm -rf skills-srcUse ~/.agents/skills/ instead of .agents/skills for a personal install.
Codex skills documentation · loads skills from .agents/skills/
Install the "smart-rebase" agent skill from https://github.com/sd0xdev/sd0x-harness/tree/main/skills/smart-rebase into .agents/skills/smart-rebase/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "smart-rebase", then confirm the skill loads.Codex copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add sd0xdev/sd0x-harness --skill smart-rebase -a cursorProject install goes to .agents/skills/; add -g for ~/.cursor/skills/.
$ gh skill install sd0xdev/sd0x-harness smart-rebase --agent cursorProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/sd0xdev/sd0x-harness.git skills-src && mkdir -p .cursor/skills && cp -r skills-src/skills/smart-rebase .cursor/skills/smart-rebase && rm -rf skills-srcUse ~/.cursor/skills/ instead of .cursor/skills for a personal install.
Cursor skills documentation · loads skills from .cursor/skills/, .agents/skills/, .claude/skills/, .codex/skills/
Install the "smart-rebase" agent skill from https://github.com/sd0xdev/sd0x-harness/tree/main/skills/smart-rebase into .cursor/skills/smart-rebase/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "smart-rebase", then confirm the skill loads.Cursor copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gemini skills install https://github.com/sd0xdev/sd0x-harness.git --path skills/smart-rebase--scope user (default) or --scope workspace; --path is the subfolder of the repo that holds the skill; --consent skips the security confirmation prompt.
$ npx skills add sd0xdev/sd0x-harness --skill smart-rebase -a gemini-cliProject install goes to .agents/skills/; add -g for ~/.gemini/skills/.
$ gh skill install sd0xdev/sd0x-harness smart-rebase --agent gemini-cliProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/sd0xdev/sd0x-harness.git skills-src && mkdir -p .gemini/skills && cp -r skills-src/skills/smart-rebase .gemini/skills/smart-rebase && rm -rf skills-srcUse ~/.gemini/skills/ instead of .gemini/skills for a personal install, then run /skills reload.
Gemini CLI skills documentation · loads skills from .gemini/skills/, .agents/skills/
Install the "smart-rebase" agent skill from https://github.com/sd0xdev/sd0x-harness/tree/main/skills/smart-rebase into .gemini/skills/smart-rebase/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "smart-rebase", then confirm the skill loads.Gemini CLI copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gh skill install sd0xdev/sd0x-harness smart-rebaseInstalls for Copilot at project scope by default; add --scope user for a personal install. Preview a skill first with gh skill preview. Needs GitHub CLI 2.90.0 or later (public preview).
$ npx skills add sd0xdev/sd0x-harness --skill smart-rebase -a github-copilotProject install goes to .agents/skills/; add -g for ~/.copilot/skills/.
$ git clone --depth 1 https://github.com/sd0xdev/sd0x-harness.git skills-src && mkdir -p .github/skills && cp -r skills-src/skills/smart-rebase .github/skills/smart-rebase && rm -rf skills-srcUse ~/.copilot/skills/ instead of .github/skills for a personal install. Commit .github/skills so cloud agent and code review can use it.
GitHub Copilot skills documentation · loads skills from .github/skills/, .claude/skills/, .agents/skills/
Install the "smart-rebase" agent skill from https://github.com/sd0xdev/sd0x-harness/tree/main/skills/smart-rebase into .github/skills/smart-rebase/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "smart-rebase", then confirm the skill loads.GitHub Copilot copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add sd0xdev/sd0x-harness --skill smart-rebase -a opencodeOpenCode documents no install command of its own. Project install goes to .agents/skills/; add -g for ~/.config/opencode/skills/.
$ gh skill install sd0xdev/sd0x-harness smart-rebase --agent opencodeProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/sd0xdev/sd0x-harness.git skills-src && mkdir -p .opencode/skills && cp -r skills-src/skills/smart-rebase .opencode/skills/smart-rebase && rm -rf skills-srcUse ~/.config/opencode/skills/ instead of .opencode/skills for a personal install.
OpenCode skills documentation · loads skills from .opencode/skills/, .claude/skills/, .agents/skills/
Install the "smart-rebase" agent skill from https://github.com/sd0xdev/sd0x-harness/tree/main/skills/smart-rebase into .opencode/skills/smart-rebase/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "smart-rebase", then confirm the skill loads.OpenCode copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
smart-rebaseSmart partial rebase for squash-merge repositories. An agent skill from sd0xdev/sd0x-harness.
Smart Rebase is an agent skill from sd0xdev/sd0x-harness. Smart partial rebase for squash-merge repositories. Auto-detect which commits to keep/drop when base branch was squash-merged into target. Use when: user says 'rebase', 'partial rebase', 'base already merged', 'smart rebase', or /smart-rebase. Not for: simple git rebase (the developer runs it — no skill executes rebase for them), merge conflict resolution (use /merge-prep), branch switching (the developer runs git checkout). Output: rebase plan table + a shell-quoted git rebase --onto command for the developer to…
Its SKILL.md is about 9.3k tokens, which your agent loads only when the skill is triggered. The skill folder holds 2 other files, including scripts (for example `scripts/smart-rebase-analyze.sh`).
It sits in Development, covering Git workflow. It works with Git and Bash. The repository describes itself as: The harness layer for Claude Code — a reference implementation of harness engineering with hook-enforced dual review, state-machine gates that survive context compaction, and… The licence is MIT.
6 steps, taken from the step headings in SKILL.md.
Read from SKILL.md and the folder at commit c9a2036. It shows what the files ask for, not the result of running them.
Pre-approves these tools, so the agent can use them without asking each time:
Bash(git:*)Bash(bash:*)ReadGrepGlobFrom allowed-tools in the SKILL.md frontmatter.
Ships 1 file in scripts/ (Shell), which the agent can run.
Shell commands in SKILL.md call:
gitbashghFrom the folder's file list and the shell code blocks in SKILL.md.
Links to these hosts (documentation or services it may open):
github.comFrom URLs in SKILL.md, links to its own repository left out.
Names no API keys, tokens, secrets or passwords.
From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.
Smart Rebase loads about 9.3k tokens when it runs. Until then it costs about 143 tokens; SKILL.md has 4,315 words of instructions outside code blocks.
Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.
The automated check found no risky patterns in SKILL.md.
Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); the scripts in this folder are not scanned.
The full file from sd0xdev/sd0x-harness at commit c9a2036, republished under its MIT licence (© sd0xdev). 4,315 words, ~9,266 tokens.
.claude/skills/smart-rebase/SKILL.md (or your agent's skills folder). This skill also uses 1 other file; get the full folder from GitHub.Analyze branch history → identify squash-merged commits → generate precise git rebase --onto command.
git rebase without squash-merge complexity — the developer runs it; no skill runs
rebase for them, this one included, and the one credential that reaches it is the user's own
message naming that execution (§ Permissions). "Use git directly" is an instruction to the human,
and this list is read by the dispatcher before § Permissions loads, so it says so here/merge-prep)git cherry-pick)In squash-merge repositories, when a feature branch is based on another branch that was already squash-merged:
main: A ─── S (squash merge of B1+B2+B3) ─── ...
↑
feature: A ─ B1 ─ B2 ─ B3 ─ F1 ─ F2 ─ F3
↑ drop (in S) ↑ keep (unique)Need: git rebase --onto main B3 feature to keep only F1-F3.
Claude must not execute git rebase — no workflow authorization lifts this, this skill's
included. The one credential that does reach it is Register #4's user-authorized execution: the
user's own message naming that execution, which this skill can neither grant, request nor infer.
rebase is a destructive operation under Anchor Register #4 (@rules/discretion.md), whose
enumerated approval workflows are a closed set: /push-ci (push), /smart-commit --execute (add + commit),
/epic-merge (rebase --onto, force-with-lease, squash-merge) and /gh-stack
(gh stack link / push / submit --auto). This skill is not on that list, so
user approval here cannot create the exception — adding a workflow to the list is itself an
Anchor-level change. This skill outputs the rebase command; the developer runs it.
Note on
allowed-tools:Bash(git:*)is granted because Steps 1–3 read history (git log,git rev-parse,git branch --show-current), andBash(bash:*)to run the analysis script — the latter cannot be narrowed to specific script paths until #9354 is resolved. A tool grant is not an authorization: what may be executed is decided by the rule above, not by what the permission string happens to permit.
Before starting, validate:
| Check | Command | Fail action |
|---|---|---|
| Not on a protected branch | git branch --show-current must not match the complete protected set defined in @rules/git-workflow.md § Prohibited (which lists the complete protected set): main, master, develop, release/*. Do not re-spell a shorter list here — a partial copy is how master and release/* reached the force-push suggestion below | Abort with warning |
| Clean working tree | git status --porcelain must be empty | Abort: "stash or commit changes first" |
| Not detached HEAD | git symbolic-ref HEAD succeeds | Abort: "checkout a branch first" |
Every command this skill outputs carries ref names, and a ref name is not display text:
git check-ref-format accepts ;, backticks, $( ) and ' inside one — all measured. A name
substituted into a command as a bare word runs whatever it contains, and a fixed pair of single
quotes is broken by a name containing '.
So every <…> slot in a command block below is written <quoted …>: substitute a shell-quoted
value, single-quoted with each ' rendered as '\''.
Shell-quoting is only half of it, and the other half is git's own option parser. Quotes are
consumed by the shell; git never sees them, so a branch named --all is still an option when it
arrives. Measured: git check-ref-format refs/heads/--all exits 0, and
git push --force-with-lease origin '--all' pushed every branch in the repository, not the one
named. The mechanism the templates below rely on is the -- / --end-of-options separator,
placed before the ref operand — not fully-qualifying it. That distinction is load-bearing for one
operand in particular: a git rebase branch operand written refs/heads/<branch> is measured
to land on a detached HEAD and never move the branch, which is the whole point of the command,
whereas the same short name after -- stops the option parser and — when the short name resolves
unambiguously — updates the ref. That qualification is not hypothetical and the separator does not
supply it: with a branch refs/heads/tags/v0.0.1 and a tag v0.0.1 both present,
git rebase --onto main main -- 'tags/v0.0.1' exits 0 and leaves the branch unmoved (measured —
docs/features/ref-name-hardening/requests/2026-08-20-ref-name-hardening-r1.md finding 1). Until the
ambiguity probe that closes this lands (that ticket's AC 1), the emitted rebase_command is not
safe to run verbatim on a repository where the current branch name could also name a tag — check
git rev-parse --verify for an is ambiguous warning on the operand before running it. So the
separator, carried in the template, is the rule — -- for git push and git rebase,
--end-of-options for git merge-base and for git log --not (Step 6); a <quoted branch> after
it stays a short name on purpose. Which spelling matters, and where, is measured — not a blanket
rule. Measured on git 2.55.0 in this repository:
| Command | -- | --end-of-options | Consequence |
|---|---|---|---|
git push, git fetch, git merge-base | accepted | accepted | Interchangeable. Picking one is a convention, not a correctness requirement |
git log … --not <name> | forces <name> to a pathspec — a name that is neither ref nor file yields nothing at exit 0 | keeps rev/path disambiguation — the same name exits 128 with ambiguous argument | Not interchangeable. --end-of-options fails closed; -- fails silently. Step 6 uses --end-of-options for this reason |
git rev-parse | — | echoes the separator literally, then resolves | Neither separator helps. Use git rev-parse --verify --quiet refs/heads/<name> |
git check-ref-format --branch | rejected (exit 129) | rejected (exit 129) | Honours neither, so the script validates a source by its full refs/heads/… form instead |
The separator terminates option parsing, never refspec parsing: git push origin -- '+main'
still reads the leading + as the force modifier, and git check-ref-format --branch '+main' exits
0, so a lexical protected-branch check passes a branch literally named +main. Emit fully-qualified
refspecs ('refs/heads/<n>:refs/heads/<n>') rather than short names wherever a + could appear.
This is the same fact § Step 5 states about the analysis script: the resolved operand is what git
acts on, never the spelling that was typed.
The slot also brings its own quotes —
never wrap one in a further fixed pair. '--base=<quoted branch-or-commit>' reads as safe and is
not: substituting feat/x;printf${IFS}PWN yields '--base='feat/x;printf${IFS}PWN'', which the
shell splits into an analysis call and a second command (measured), and a name containing ' turns
it into a syntax error. Write the fixed prefix unquoted, adjacent to the slot:
--base=<quoted branch-or-commit>. Where the analysis script has already
produced the string — its rebase_command field, and the git fetch … line in its failure
message — copy that string rather than rebuilding it; sh_quote in the script is the same rule,
already applied. Reasons in full: docs/features/ref-name-hardening/4-implementation.md § 1.
Step 1: Analyze → run script to detect commits
Step 2: Identify → determine keep/drop boundary (cut point)
Step 3: Display → output rebase plan
Step 4: Confirm → user reviews
Step 5: Output → print the rebase command for the developer to run
Step 6: Verify → confirm history is correctDiagnose first — this analyzer writes refs, and a configured negative refspec changes what its refresh can reach. Round 59 corrected this paragraph: it used to call that an open defect and tell the operator not to run the analyzer at all. The write defect is closed (§ Names in commands has the shipped behaviour), so what the check below buys is a reason — it names the configuration that will make a refresh transfer nothing, before the analyzer refuses and the operator has to work out why. Run it for each remote the analyze step will probe:
# Capture, status-check, THEN match — a pipeline exits with `grep`'s status, so a `git config`
# that fataled would report an ordinary no-match. Measured on git 2.55.0:
# `GIT_CONFIG_COUNT=1 git config --get-all remote.origin.fetch` exits 128, and the same command
# piped into `grep -q '^\^'` exits 1 — the exact status a valid config with no negative refspec
# produces. Exit 1 from git itself means the key is simply unset, which is a real answer;
# anything else means the configuration could not be read at all.
remote=<quoted remote> # the remote the analyzer will probe — `origin` unless --target names another
if refspecs=$(git config --get-all "remote.${remote}.fetch"); then rc=0; else rc=$?; fi
if [[ "$rc" -ne 0 && "$rc" -ne 1 ]]; then
echo "⛔ cannot read remote.${remote}.fetch (git exited $rc) — do not run the analyzer" >&2
elif printf '%s\n' "$refspecs" | grep -q '^\^'; then
echo "⚠️ NEGATIVE REFSPEC CONFIGURED on $remote — if the analyzer aborts on an empty transfer, this is why" >&2
fi
[[ "$rc" -eq 0 || "$rc" -eq 1 ]] # the fence's exit status IS this line — see belowremote is bound, and the exit status answers exactly one question. Both were wrong in the
first version of this gate and wrong in the same direction. ${remote} was left unbound, so running
the fence verbatim queried remote..fetch — a key that is never set, so the gate reported nothing
about the remote it was supposed to guard. And the block ended at an echo on both refusal paths
while the safe path fell out of a failed grep, so $? read 1 for the one case that may proceed
and 0 for both cases that may not — exactly inverted for any wrapper.
The status is now a trailing [[ ]] rather than a true / false terminator, and that is a
correctness fix rather than a style one. Round 61 measured what the change's own threat model
(docs/features/push-gate-optin/4-implementation.md § 4.7) already implies: bash imports functions
from the environment, and a function outranks a builtin, so true, false and exit all
return whatever an imported BASH_FUNC_true%% says — measured on git 2.55.0 / bash 3.2, each
returned 7 under a shadow declaring return 7. [[ is a shell keyword, resolved by the parser
before any name lookup, so it is the one form here that cannot be replaced. The line therefore says
the verdict directly instead of delegating it to a word.
Two consequences worth stating rather than discovering. The echos can still be shadowed, so a
caller may see no message; the status is the contract. And the terminators are deliberately not
exit, both because the fence may be sourced and because exit is shadowable in exactly the same
way.
What the [[ ]] does and does not buy — round 62 narrowed this. An earlier wording here called
the status "the part that cannot be forged", which claims more than the keyword delivers. What
cannot be replaced is the verdict form: no imported function can invert the last line, so the
inversion this gate shipped with in its first version is now structurally impossible. The verdict's
input is another matter — $rc comes from a bare git, a command word like any other, and
under BASH_FUNC_git%%='() { return 0; }' an unreadable configuration reports rc=0 and the fence
exits 0. That residue has no fix at this layer and no pretence of one: the push fences answer it by
re-executing under bash -p, which discards function imports outright, and a markdown fence run in
the operator's own shell cannot ask for -p (docs/features/push-gate-optin/4-implementation.md
§ 4.3 records the same limit for the caller side of the push gate). The backstop is elsewhere and is
independent: the analyzer performs the refresh itself and aborts on an empty transfer, so a forged
clean read here buys a run that still stops — which is the same division of labour as the ⚠️
above, where this block labels and the analyzer decides.
The question the status answers is "could the configuration be read?", and nothing more. Round
60 narrowed it: a hit used to return false as well, which put the gate in the business of
predicting an outcome it cannot see. A negative refspec only empties the transfer when it cancels
every positive mapping on the refresh line — ^refs/heads/wip/* beside
+refs/heads/*:refs/remotes/origin/* cancels one prefix and leaves the rest reachable — and this
block reads the configured spelling, not the intersection. Refusing on any hit therefore stopped
runs that would have worked, and the operator's only route past it was to ignore the gate, which is
how a check stops being read at all.
So a hit is a label, not a verdict: it prints ⚠️ and Step 1 proceeds. The analyzer is the one
that can answer, because it is the one that performs the refresh — its explicit refresh carries that
remote's negatives on its own command line, so git applies its own matching where the write happens
and no ref is refreshed from a source the configuration excluded; and when every positive on that
line is cancelled, the refresh transfers nothing and the analyzer aborts rather than planning from
stale history. That abort is the refusal, and the ⚠️ line above it is what saves the operator from
having to work out why. Resolve the target by hand, or run against a remote with no negative
refspec, if you need a plan anyway. The one case that still refuses here is the ⛔ branch: the
configuration could not be read, so the block has no answer to give, and treating that as a clean
no-hit is what would let an unreadable configuration read as a clean bill of health.
# --target is optional; omit it to auto-detect against origin/main.
bash skills/smart-rebase/scripts/smart-rebase-analyze.sh --target=<quoted target>The --target value is a ref name and follows § Names in commands: the joined
--target=<quoted target> form, never a bare --target <value> — a target named
origin/main;printf${IFS}PWN is a legal ref (check-ref-format accepts it) and a bare, unquoted
operand would run the second command when the line is pasted.
Auto-detect mode uses git cherry to find commits already cherry-picked to target. Squash merges cannot be detected by git cherry — proceed to Step 2.
Case A — User provides base branch or commit
# Resolve the common ancestor as cut candidate
git merge-base --end-of-options <quoted base-branch> HEAD
# Or specify the cut point commit directly — with the SAME --target Step 1 used
bash skills/smart-rebase/scripts/smart-rebase-analyze.sh --target=<quoted target> --base=<quoted branch-or-commit>Every --base re-run carries the --target Step 1 used. The analyzer resets its target to
origin/main on every invocation (skills/smart-rebase/scripts/smart-rebase-analyze.sh, where
TARGET is initialised before argument parsing), so a base-only re-run after an origin/develop
analysis plans — and emits a rebase command — against origin/main. When Step 1 used the default,
--target=origin/main restates it; it is never wrong to pass it.
Case B — Inference needed
target_new squash merge commit messagescommits messages in current branch--base and the same --target as Step 1 (Case A's command)Case C — git cherry detected all
When cherry_dropped > 0, detected commits can be dropped. Verify cut point is contiguous (all drops must precede all keeps).
## Rebase Plan
| Item | Value |
| -------------- | --------------------------------------- |
| Current branch | feat/my-feature |
| Target | origin/main (dd21265c) |
| Cut point | 06a7fae6 |
| Keep | 3 commits |
| Drop | 15 commits (already in main via squash) |
### Commits to Keep
1. `57d7898a` feat: Add error classification framework
2. `05c11119` docs: Document classification rules
3. `da987681` fix: Correct classification accuracy
### Commits to Drop (already in main)
1. `f76209f4` docs: Add RPC optimization design
...Display plan and wait for user confirmation before proceeding.
One command is output, and the script already wrote it. A successful analysis emits
rebase_command, with every name shell-quoted — copy that field verbatim rather than rebuilding it
(§ Names in commands). Verbatim means "do not re-quote it", not "run it unchecked." Until the
ambiguity probe lands (see the warning above), first confirm the operand is unambiguous:
# Bind once, then use the variable — § Names in commands. The slot brings its own quotes,
# so it may sit alone on the right of `=`, but it must NEVER be nested inside a further
# fixed pair: `"refs/heads/<quoted branch>"` renders as `"refs/heads/'main'"`, where the
# single quotes are literal ref-name characters. Measured: that pattern matches 0 refs for
# an ordinary unique branch, so the guard below would refuse every legitimate rebase.
branch=<quoted branch>
ok=1
git rev-parse --verify --quiet "refs/heads/$branch" >/dev/null ||
{ echo "⛔ not a branch"; ok=0; }
# Exact refs, one --verify each. NOT `for-each-ref <pattern>`: its patterns match a
# refname *prefix* at a path boundary, so `refs/codex` matches `refs/codex/turn-diffs/x`
# and an ordinary branch named `codex` counts 2 and is refused. Measured in this very
# repository, which carries such a subtree (`git for-each-ref refs/codex` prints it while
# `git show-ref --verify refs/codex` fails) — a false refusal, on a legal branch name.
n=0
for r in "refs/$branch" "refs/tags/$branch" "refs/heads/$branch" \
"refs/remotes/$branch" "refs/remotes/$branch/HEAD"; do
git show-ref --verify --quiet -- "$r" && n=$((n+1))
done
case "$n" in
1) ;;
*) echo "⛔ short name matches $n refs — do not run"; ok=0 ;;
esac
short=$(git rev-parse --verify --quiet "$branch")
head=$(git rev-parse --verify --quiet "refs/heads/$branch")
if [[ -z "$head" || "$short" != "$head" ]]; then
echo "⛔ short name resolves to ${short:-nothing}, not the branch ${head:-nothing} — do not run"; ok=0
fi
# OID equality is not ref identity, and the gap is reachable. A branch literally named
# `FETCH_HEAD` sits under the pseudo-ref of the same name, which wins resolution — but if
# both currently point at the same commit, the count is 1 and `$short` = `$head`, so the two
# checks above clear an operand that still denotes the pseudo-ref. Measured in a scratch repo:
# count=1, OIDs equal, guard PASSES, while git itself calls the name ambiguous. So ask git what
# the name *is*, not only what it points at.
sym=$(git rev-parse --verify --quiet --symbolic-full-name --end-of-options "$branch" 2>/dev/null)
if [[ "$sym" != "refs/heads/$branch" ]]; then
echo "⛔ short name denotes ${sym:-an ambiguous or non-branch ref}, not refs/heads/$branch — do not run"; ok=0
fi
# Read the VALUE, never `$?`: on the ambiguous name git printed `error: refname … is ambiguous`
# to stderr, wrote nothing to stdout, and still **exited 0**. A `|| { ... }` on this command
# would have cleared exactly the case it was added to catch.
[[ "$ok" -eq 1 ]] # ← the block's exit status. Zero ⇔ every check passedThe last line is the point, not a formality. Each check above reports and keeps going, so the
developer sees every reason at once rather than the first — but a block that only prints exits 0
whatever it found, and anything reading $? (a wrapper script, a && chain, a copy pasted into a
runner) reads "clear to rebase" off a block that just refused three times. [ "$ok" -eq 1 ] as the
final command makes the block's own status the verdict. It is deliberately not exit 1: this
snippet is meant to be pasted, and exit in an interactive shell closes the terminal.
And the verdict is stated by keywords, not by [. Round 63. [ is a builtin, so an imported
BASH_FUNC_[%% function outranks it and answers every test in the block — including the last one —
for the caller. Measured on bash 3.2.57: under that shadow, a name deliberately colliding with a tag
and resolving to a non-branch produced no refusal message and exit status 0. [[ and case are
keywords, resolved by the parser before any name is looked up, so no imported function can reach
them; that is the whole reason the four tests above are written the way they are, and case rather
than [[ … -eq … ]] for the count because -eq inside [[ ]] is arithmetic — an empty operand
would read as 0 and a non-numeric one would be dereferenced as a variable name.
The same narrowing as the Step 1 gate applies here, and for the same reason: what cannot be replaced
is the verdict form. The verdict's inputs are bare git invocations, and a shadowed git
forges those exactly as it forges Step 1's read. There is no fix for that at this layer — a fence
pasted into the operator's own shell cannot ask for bash -p — and claiming otherwise is the error
round 62 corrected once already.
The second check counts refs; it must never read git's message, and must never read only its
exit status. Both of the obvious shortcuts are measured failures on git 2.55.0, in a repository
holding both a branch and a tag named dup:
| Shortcut | Measured | Why it fails |
|---|---|---|
git rev-parse --verify dup 2>&1 | grep -q 'is ambiguous' | matches today | Git marks that string for translation. It survived LC_ALL=zh_CN.UTF-8 here only because that catalogue leaves the clause untranslated — a locale that does translate it turns this guard silently green |
git rev-parse --verify --quiet dup; [ $? -ne 0 ] | exit 0 | Git treats a branch/tag collision as a warning: it recovers from, resolving to one of them and printing a sha. Reading the status alone therefore fails open on the very case the check exists for — strictly worse than the string match |
The five names are checked as exact refs, and that is a correctness requirement rather than a
style choice. for-each-ref's pattern argument matches a prefix at a path boundary: the
pattern refs/codex matches refs/codex/turn-diffs/… as readily as an exact refs/codex. Custom
ref namespaces under refs/<something>/ are ordinary — this repository carries one — so the
pattern form counts an unrelated subtree as a second hit and refuses a legal branch. Measured here:
| Command | Result |
|---|---|
git for-each-ref --format='%(refname)' refs/codex | prints refs/codex/turn-diffs/… |
git show-ref --verify refs/codex | fatal: 'refs/codex' - not a valid ref |
pattern count, with a branch codex present | 2 → refused |
exact show-ref --verify count, same repository | 1 → proceeds |
The direction of the error decides the fix: refusing a legitimate rebase is loud and recoverable, but it teaches the operator to work around the guard, and a guard routinely worked around stops guarding the case it was written for. Exact verification costs the same five commands.
The five names are not git's whole DWIM set, which is why a third check exists. Git resolves
$GIT_DIR/<name> pseudo-refs — HEAD, FETCH_HEAD, ORIG_HEAD, MERGE_HEAD and their siblings —
before it looks in refs/** at all, and for-each-ref iterates the ref store, so it
structurally cannot see them. Measured on git 2.55.0, in a repository holding a branch named
FETCH_HEAD at commit B and an ordinary .git/FETCH_HEAD naming commit A:
| Check | Result | Reading |
|---|---|---|
git check-ref-format --branch FETCH_HEAD | exit 0 | A legal branch name — this is not a hypothetical |
git rev-parse --verify --quiet FETCH_HEAD | A | The pseudo-ref wins; the branch is invisible |
| the five-name exact-ref count | 1 | "Unique — proceed." The count fails open here |
short vs refs/heads/$branch | A ≠ B | The collision, stated as the fact that matters |
So the count answers "how many refs bear this name" and the third check answers "does the name git will actually use point at the branch you meant" — neither subsumes the other. A branch and a tag sitting on the same commit pass the third check while the count refuses (the collision is real even though today's shas agree); a pseudo-ref passes the count while the third check refuses. Both lines stay.
Why not test whether $(git rev-parse --git-path "$branch") exists — the obvious way to spot a
pseudo-ref. Measured in the same repository: git rev-parse --git-path config returns .git/config,
which exists in every repository, so that test refuses a perfectly ordinary branch named config
(a legal name — git check-ref-format --branch config exits 0). Comparing resolutions costs the
same and refuses nothing it must accept.
0 from the count is unreachable here — line 1 already proved refs/heads/<branch> exists — so
anything but 1 is a genuine collision, and this is the shape the document warns can exit 0 while
leaving the intended branch unmoved.
If either line reports, hand the developer the plan table without the command and say why:
rebase_command → git rebase --onto 'refs/remotes/origin/main' '2692ede' -- 'feat/x'There is no fetch command to output. When the target is remote-tracking, the analysis performs
the refresh itself before building the plan; the developer has nothing left to fetch. A git fetch
string appears in exactly one place — inside the error payload when that refresh fails, already
shell-quoted — and in that case there is no plan and this step is not reached. Show that string
only when relaying such a failure, and copy it rather than rebuilding it.
For reading, that internal fetch is:
git fetch --refmap= --no-tags --no-recurse-submodules --quiet -- <quoted remote> <quoted refspec> <quoted negatives>...Every part of it is load-bearing: a bare branch argument is a refspec, so the configured
remote.<name>.fetch decides where the fetched ref lands and a value like +main:refs/heads/main
overwrites a local branch. --refmap= ignores that configuration, the fully-qualified refspec names
a destination under refs/remotes/, --no-tags --no-recurse-submodules close the two write paths
the refspec does not govern, and -- (after --quiet, never before it) makes a remote named
-evil an operand instead of a flag. Reasons in full:
docs/features/ref-name-hardening/4-implementation.md § 1.
Configured negatives are carried on the command line, not re-implemented. --refmap= discards
remote.<name>.fetch, so the positive mapping is rebuilt here — and a negative rebuilt the same way
would be wrong, because git normalizes a short positive source but does not DWIM a negative one.
So the negatives are not rebuilt: the selected remote's configured ^… entries are appended to this
exact command, and git does its own matching at the point the write happens. Two consequences the
plan depends on:
| Situation | What happens |
|---|---|
| A negative cancels the mapping | The fetch exits 0 and transfers nothing — measured: FETCH_HEAD is written at 0 bytes, where a real update, an already-up-to-date fetch and an irrelevant negative all write 66. The analyzer reads that postcondition and aborts rather than planning from whatever the ref held before |
| A short target source | It is never matched against the negatives at all — git resolves a short source across the remote's whole namespace, and only a negative spelled in the namespace it lands in cancels it, which nothing local can decide. So a short source is treated as not provably excluded, never as matches nothing |
The error payload for the empty-transfer case names the negatives it passed, so the developer can
re-run the same command by hand. What is still open in docs/features/ref-name-hardening/ r1 AC 1
is the redesign — replacing the positive reconstruction with git-mediated resolution
(git ls-remote + an ambiguity-aware probe + git fetch --dry-run) — not this write path.
Output the command for the developer to run. Claude does not execute it — see § Permissions;
no confirmation the user gives in this skill changes that. A confirmation is an answer to this
skill's question; the credential that reaches rebase is the user's own message naming the
execution, which this skill neither asks for nor infers.
# Confirm history is correct
git log --oneline -10
# Confirm commit count — in TWO steps, never as `git log … | wc -l`. A pipeline exits with
# `wc`'s status, so a `git log` that fataled on an unreadable target still reports success
# and a plausible **0**. Measured here: an unknown target prints `fatal: ambiguous argument`,
# the pipeline exits 0, and the count reads 0 — the guard fails open on exactly the input
# `--end-of-options` was added to catch, and the next step recommends a force push.
# Same shape as `skills/epic-merge/SKILL.md` § Phase 0.
range=$(git log --oneline HEAD --not --end-of-options <quoted target>) || {
echo "⛔ cannot read HEAD --not <target> — do not push" >&2
exit 1
}
# An empty range is a count, not a failure: `grep -c` would exit 1 on it and abort a
# caller running under `set -e` on a branch that simply has no unique commits.
if [ -z "$range" ]; then echo 0; else printf '%s\n' "$range" | wc -l; fiOn success, suggest the force push. Prefer routing it through /push-ci --force-with-lease — that
is the authorized workflow (Anchor Register #4), and its Phase 0 hard-aborts a protected branch paired
with a force form, which a hand-typed command does not.
For a developer pushing by hand, the suggested command carries both guards:
git push --force-with-lease --force-if-includes origin -- <quoted refspec><quoted refspec> is refs/heads/<branch>:refs/heads/<branch> — both halves spelled out, the
whole operand shell-quoted as one unit. It is one slot rather than a refs/heads/ prefix wrapped
around <quoted branch>, because a slot in this document already carries its own quotes and
nesting a second pair around it is exactly what the quoting rule above forbids.
Why the full source:destination form, and not the bare branch name. -- ends option parsing,
not refspec parsing — after it, git still reads the operand as a refspec, where a leading +
means force and : splits source from destination. git check-ref-format refs/heads/+main exits 0,
so +main is a legal branch name; written bare after -- it is read as "force-push main", which
rewrites the protected branch while the name the developer approved was +main. The protected-name
comparison misses it too, because it compares +main against main. Spelling both halves removes
the reading: refs/heads/+main:refs/heads/+main pushes the branch that was actually named.
/push-ci and /epic-merge render the same shape for the same reason — skills/epic-merge/SKILL.md
§ Names in commands carries the measurement on a real remote.
--force-with-lease alone is not sufficient: it checks the remote against your last fetched ref, so
a collaborator commit already pulled by any background fetch satisfies the lease and is overwritten
with exit 0 (measured — git-push(1) says this form "interacts very badly with anything that runs
git fetch in the background").
What the pair closes, stated narrowly, because the wider claim is false. --force-if-includes
asks whether the remote tip is reachable from any reflog entry of the local branch — not whether
the history you are about to push still contains it. Measured on a real remote: fetch the
collaborator's commit, check it out (the reflog now holds it), then rewrite to a history that drops
it, and the push still overwrites it with exit 0. /smart-rebase runs immediately before exactly
such a rewrite, so that ordering is the normal case here, not a contrived one.
So the pair closes one window — the collaborator commit brought in by a background fetch that the
operator never touched — and leaves the reflog-reachability path open. Neither flag addresses a
collaborator who has the branch checked out while its history is rewritten; nothing on the push side
can see that. Full measurements:
docs/features/push-gate-optin/requests/2026-08-20-push-ci-force-with-lease-r5.md.
| Scenario | Action |
|---|---|
| Already squash-merged | git rebase --skip (dropped commit) |
| Real content conflict | Manual resolve → git rebase --continue |
| Cannot resolve | git rebase --abort to restore original state |
git rebase on an approval given in this skill — not an AskUserQuestion
answer, not a confirmed plan. Anchor Register #4's workflow list is closed and this skill is not on
it; the one credential that reaches rebase is the user's own message naming that execution
(§ Permissions), which this skill never asks for@rules/git-workflow.md § Prohibited
(main, master, develop, release/*), never a shorter list restated here--force-with-lease. It compares the remote ref against your last
fetched value, not against what you have integrated, so a collaborator commit that any
background git fetch already pulled satisfies the lease and is overwritten with exit 0 —
measured, see docs/features/push-gate-optin/requests/2026-08-20-push-ci-force-with-lease-r5.md.
--force-if-includes closes that one window and no more — it tests reachability from any
reflog entry, not from the history being pushed, so a commit you checked out and then dropped is
still overwritten with exit 0 (measured, same ticket). Suggest both flags and state the residual;
bare --force stays forbidden outrightRebase plan table with keep/drop commits:
--base: includes the shell-quoted git rebase --onto command — for the developer to run after Step 5's ambiguity check, and withheld when that check reports--base follow-up@rules/git-workflow.md, clean tree, not detached)--force-with-lease and --force-if-includes used (never --force, never the
bare lease — a checklist that accepts the lease alone green-lights exactly what § Prohibited bans)# Auto-detect (cherry-pick scenarios)
/smart-rebase
# Specify base branch (squash-merge scenarios)
/smart-rebase --base fix/feature-xyz
# Specify non-main target
/smart-rebase --target origin/develop --base fix/hotfix-123| File | Purpose | When to Read |
|---|---|---|
| smart-rebase-analyze.sh | Analysis script | Step 1 |
© sd0xdev, MIT. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file
SKILL.md and 1 other file (scripts) in skills/smart-rebase of sd0xdev/sd0x-harness.
Open the folder on GitHubat commit c9a2036
Smart Rebase next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.
| Skill | Stars | Used in | Tokens | Auto-check | Licence | Repo updated |
|---|---|---|---|---|---|---|
| Smart Rebase this skillsd0xdev/sd0x-harness | 192 | — | ~9.3k | Automated safety check: Pass | MIT | |
| Wednesday Gitwednesday-solutions/ai-agent-skills | 170 | — | ~702 | Automated safety check: Pass | MIT | |
| Speckit Git Featurekunstmusik/blue | 154 | 4 repos | ~861 | Automated safety check: Pass | GPL-3.0 | |
| Trace And Isolaterohitg00/skillkit | 1.5k | — | ~1.8k | Automated safety check: Pass | Apache-2.0 | |
| Speckit Git Initializekunstmusik/blue | 154 | 5 repos | ~438 | Automated safety check: Pass | GPL-3.0 | |
| Tapd Iteration InitTencentBlueKing/bk-bcs | 840 | — | ~1.3k | Automated safety check: Pass | Custom licence |
wednesday-solutions/ai-agent-skills
Unified Git workflow. An agent skill from wednesday-solutions/ai-agent-skills.
kunstmusik/blue
Create a feature branch with sequential or timestamp numbering
rohitg00/skillkit
Applies systematic tracing and isolation techniques to pinpoint exactly where a bug originates in code.
kunstmusik/blue
Initialize a Git repository with an initial commit. An agent skill from kunstmusik/blue.
TencentBlueKing/bk-bcs
迭代执行流水线启动器。分三个阶段执行:信息检查(验证 git 环境、获取迭代信息、 解析迭代分支)→ 恢复检测(已有状态文件时判定恢复策略)→ 新流程初始化(创建分支、 迭代目录和 iteration-state.json)。
secondsky/claude-skills
Claude Code Bash tool patterns with hooks, automation, git workflows.
sd0xdev/sd0x-harness
Write an Architecture Decision Record (ADR) for a feature — Context / Decision / Status / Consequences / Alternatives, filed as docs/features/<feature/adr-<NNN-<title.md with a 3-digit zero-padded…
sd0xdev/sd0x-harness
Load GitHub PR review comments into AI session — analyze, triage, plan.
sd0xdev/sd0x-harness
Change-aware next step advisor. An agent skill from sd0xdev/sd0x-harness.
sd0xdev/sd0x-harness
Obsidian vault integration via official CLI. An agent skill from sd0xdev/sd0x-harness.
sd0xdev/sd0x-harness
Agent-driven workflow orchestration (v1 report-only). An agent skill from sd0xdev/sd0x-harness.
sd0xdev/sd0x-harness
Post friendly review comments to a GitHub PR — prepare locally, preview, then submit as atomic review.
Categories
Smart partial rebase for squash-merge repositories. An agent skill from sd0xdev/sd0x-harness. Smart Rebase is an agent skill from sd0xdev/sd0x-harness. Smart partial rebase for squash-merge repositories.
Smart Rebase fits situations like: : user says rebase; base already merged.
Run `npx skills add sd0xdev/sd0x-harness --skill smart-rebase -a claude-code`. Or copy the skill folder (skills/smart-rebase in sd0xdev/sd0x-harness) into .claude/skills/smart-rebase in your project. Claude Code loads it when a task matches its description.
Run `npx skills add sd0xdev/sd0x-harness --skill smart-rebase -a codex`. Or copy the skill folder (skills/smart-rebase in sd0xdev/sd0x-harness) into .agents/skills/smart-rebase in your project. Codex loads it when a task matches its description.
Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add sd0xdev/sd0x-harness --skill smart-rebase -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/smart-rebase, .gemini/skills/smart-rebase, .github/skills/smart-rebase and .opencode/skills/smart-rebase in your project.
Going by SKILL.md and its folder, Smart Rebase needs a shell for the scripts in its folder and the command-line tools its instructions call (git, bash and gh). Our summary lists: A Bash shell. Its frontmatter pre-approves these tools: Bash(git:*), Bash(bash:*), Read, Grep, Glob.
SKILL.md names 1 domain. As links in the text: github.com. This is read from the text; nothing was executed.
Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. The check reads SKILL.md only: the scripts in the folder are not scanned, so read them before running anything.
Smart Rebase is published under the MIT licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.
About 9.3k tokens (SKILL.md is roughly 37k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.
Skills that share tags, products or a category with Smart Rebase: Wednesday Git (wednesday-solutions/ai-agent-skills, 170 stars), Speckit Git Feature (kunstmusik/blue, 154 stars), Trace And Isolate (rohitg00/skillkit, 1.5k stars) and Speckit Git Initialize (kunstmusik/blue, 154 stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.
sd0xdev (a GitHub user) maintains it in sd0xdev/sd0x-harness, which has 192 GitHub stars. The repository holds 91 skills in this directory. The repository was last updated on October 6, 2026.
Source: sd0xdev/sd0x-harness on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.