Skip to content

fix(toolchain): resolve a present remote for default-branch detection instead of assuming origin - #528

Merged
kyle-sexton merged 4 commits into
mainfrom
fix/483-toolchain-default-branch-remote
Jul 19, 2026
Merged

fix(toolchain): resolve a present remote for default-branch detection instead of assuming origin#528
kyle-sexton merged 4 commits into
mainfrom
fix/483-toolchain-default-branch-remote

Conversation

@kyle-sexton

Copy link
Copy Markdown
Contributor

Summary

/toolchain:check and /toolchain:lint, when the working tree is clean, fall back to a branch diff against the default branch. Resolving that default branch depends on picking the right remote. Both call sites read the current branch's tracking remote (branch.<name>.remote) and, when it was unset, forced REMOTE=origin. On an unpushed feature branch in a clone made with a differently named remote (git clone -o vendor), there is no tracking remote and no origin, so every default-branch probe failed and the branch diff was silently skipped ("branch diff unavailable"). The common case (a pushed branch, or a plain origin clone) was unaffected, and the failure degraded gracefully rather than producing a wrong diff.

Fix

When the branch has no tracking remote, select a remote that is actually present instead of blindly assuming origin.

Before:

[[ -z "$REMOTE" || "$REMOTE" == "." ]] && REMOTE=origin

After:

if [[ -z "$REMOTE" || "$REMOTE" == "." ]]; then
  REMOTE=origin
  git remote | grep -qx origin || REMOTE=$(git remote | head -n1)
fi

This preserves the common-case behavior exactly — origin is still chosen when it is present — and only diverges when origin is absent, in which case the first present remote is used. When no remote exists at all, $REMOTE is empty and the existing rev-parse --verify guard skips the branch-diff path, so detection still degrades gracefully. The stale prose in both skills that claimed the old origin fallback "still resolves" a non-origin clone is corrected to describe present-remote selection. The change is applied identically in skills/check/SKILL.md and skills/lint/SKILL.md.

Verification

Repo-pinned gates on the changed files — all clean:

  • shellcheck (repo .shellcheckrc) on the extracted detection block: clean
  • markdownlint-cli2 on both SKILL.md files and CHANGELOG.md: 0 error(s)
  • editorconfig-checker on all four changed files: clean
  • typos on all four changed files: clean

Empirical demonstration on the exact reported scenario — a git clone -o vendor (no origin) with an unpushed feature branch (branch.feature.remote unset) and one committed change f.txt:

remotes present: vendor
branch.feature.remote: UNSET

===== OLD block (REMOTE=origin assumption) =====
REMOTE=origin
branch diff unavailable (could not detect default branch)

===== NEW block (present-remote selection) =====
REMOTE=vendor
DEFAULT_BRANCH=main
f.txt

Common-case regression check — with origin present alongside another remote, origin is still selected (clone -o origin (+extra) -> REMOTE=origin); only when origin is absent is another present remote chosen (clone -o vendor (+extra) -> REMOTE=other).

Version bumped 0.4.1 -> 0.4.2 (patch, bug fix) with a matching CHANGELOG.md entry.

Related

This fix is deliberately toolchain-local. The broader, correct root-cause fix that #436 and #442 also need is a single shared default-branch/remote helper (the gh repo view --json defaultBranchRef authoritative fallback that repo-hygiene's git-tree-reset.sh already uses). That spans multiple plugins and version files, so it is left as the tracked cross-plugin opportunity rather than pulled into this patch.

Closes #483

🤖 Generated with Claude Code

… instead of assuming origin

The clean-working-tree branch-diff fallback in /toolchain:check and
/toolchain:lint forced REMOTE=origin whenever the current branch had no
tracking remote (an unpushed feature branch). In a clone made with a
differently named remote (git clone -o vendor) that has no origin, every
default-branch probe then failed and the branch diff was skipped. Both call
sites now keep origin when present and otherwise select the first present
remote, so detection resolves against a remote that actually exists; it still
degrades gracefully when no remote is present. Stale prose asserting the old
origin fallback "still resolves" a non-origin clone is corrected in both files.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011V31qpAHP3jfs76B9d5Rfo
@claude

claude Bot commented Jul 19, 2026

Copy link
Copy Markdown

Claude finished @kyle-sexton's task in 1m 28s —— View job


PR Review: fix(toolchain) — present-remote selection for default-branch detection

  • Gather PR context and changed files
  • Review skills/check/SKILL.md changes
  • Review skills/lint/SKILL.md changes
  • Review plugin.json version bump
  • Review CHANGELOG.md entry
  • Check shell logic correctness and edge cases
  • Verify alignment with CLAUDE.md guidelines
  • Post final review

Summary

Clean, minimal, correctly-scoped bug fix. The shell logic is sound, all edge cases degrade gracefully, and the common case is preserved exactly. No issues that would block merging.


Shell logic — correctness and edge cases

The new block in both SKILL.md files:

if [[ -z "$REMOTE" || "$REMOTE" == "." ]]; then
  REMOTE=origin
  git remote | grep -qx origin || REMOTE=$(git remote | head -n1)
fi

Stepping through each scenario:

State grep -qx origin Result Correct?
No remotes at all fails; head -n1"" REMOTE=""rev-parse --verify guard skips branch-diff ✅ graceful
origin is the only remote succeeds REMOTE=origin (unchanged) ✅ same as before
Non-origin remote only (e.g. vendor) fails; head -n1"vendor" REMOTE=vendor ✅ fixes the reported bug
origin present alongside others succeeds REMOTE=origin (unchanged) ✅ common-case preserved
Multiple remotes, none origin fails; head -n1 → first remote REMOTE=<first> ✅ best-effort, correct

grep -qx (quiet + exact-line) is the right tool here — it avoids false matches on names like not-origin or my-origin.

The "set-origin-first, then check" pattern reads slightly backwards (assign then maybe overwrite), but it makes the origin-as-default intent explicit and is fully equivalent to a more symmetric if/else. Not worth changing.


Duplication across check/lint SKILL.md

The same block is duplicated in two files. The PR body calls this out explicitly and references #436/#442 as the tracked cross-plugin opportunity for a shared helper. That's the right call — pulling in a shared helper is a cross-version, cross-plugin change that would widen the scope of this patch substantially. The duplication is contained (two identical blocks) and appropriate for a targeted bug fix.


Documentation

The prose update is accurate and more precise than what it replaces. The old text claimed git clone -o vendor "still resolves," which was only true for pushed branches (where branch.<name>.remote is set to vendor). The new text correctly distinguishes the tracking-remote path from the no-tracking-remote fallback and notes that git clone -o vendor now resolves even before the branch is pushed.


CHANGELOG and versioning

  • Entry follows the Keep a Changelog format used in this file. ✅
  • Root cause, old behavior, new behavior, and graceful-degradation note are all present. ✅
  • Patch bump 0.4.10.4.2 is appropriate for a bug fix with no behavior change on the common path. ✅

Alignment with CLAUDE.md guidelines

Criterion Status
Repo-agnostic — no hardcoded paths or remote names ✅ improves this
Configurable without editing the plugin ✅ unchanged
Plugin-form-safe — no ../ reach-outs
Versioned with explicit semver bump
Security — no user-controlled data injected into shell commands

The grep -qx origin literal string and git remote | head -n1 pipeline take no user input and carry no injection surface.


Minor observation (no action needed)

git remote | head -n1 output order follows .git/config insertion order (typically the order remotes were added). When multiple non-origin remotes are present, the "first" isn't necessarily the "right" one — but for this use case (locating any valid remote to probe for the default branch) it's the right trade-off. A smarter heuristic (e.g., preferring upstream) would add complexity without a clear win.


Verdict: LGTM. The fix is correct, well-scoped, and the documentation and changelog accurately describe the change.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 442145cb2f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread plugins/toolchain/skills/check/SKILL.md Outdated
…miter

The default-branch resolution stripped the `$REMOTE/` prefix with
`sed "s#^$REMOTE/##"`. When the selected remote name contains `#`
(a Git-legal character, e.g. `foo#bar`), that `#` collides with the
sed delimiter and corrupts DEFAULT_BRANCH (`foo#bar/main` -> `bar/#bar/main`),
so the rev-parse guard silently skips the branch diff. Both `/toolchain:check`
and `/toolchain:lint` now strip the prefix with `${DEFAULT_BRANCH#"$REMOTE/"}`,
which treats the remote name literally regardless of its characters.

Addresses codex P2 on #528.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@claude

claude Bot commented Jul 19, 2026

Copy link
Copy Markdown

Claude finished @kyle-sexton's task in 1m 47s —— View job


PR Review: fix(toolchain) — present-remote selection + sed delimiter fix

  • Gather PR context and changed files
  • Review skills/check/SKILL.md changes (HEAD)
  • Review skills/lint/SKILL.md changes (HEAD)
  • Assess Codex finding: sed delimiter issue with remote names containing #
  • Review plugin.json version bump
  • Review CHANGELOG.md entry
  • Post final review

Shell logic — correctness and edge cases

Both SKILL.md files now contain the same corrected block:

REMOTE=$(git config "branch.$(git branch --show-current | tr -d '\r').remote" 2>/dev/null | tr -d '\r')
if [[ -z "$REMOTE" || "$REMOTE" == "." ]]; then
  REMOTE=origin
  git remote | grep -qx origin || REMOTE=$(git remote | head -n1)
fi
DEFAULT_BRANCH=$(git symbolic-ref --short "refs/remotes/$REMOTE/HEAD" 2>/dev/null)
DEFAULT_BRANCH=${DEFAULT_BRANCH#"$REMOTE/"}

Fix 1 — Present-remote selection (check/SKILL.md:61–64, lint/SKILL.md:63–66):

State grep -qx origin result $REMOTE Correct?
No remotes at all fails; head -n1"" "" → rev-parse guard skips ✅ graceful
origin only succeeds origin (unchanged) ✅ common case
Non-origin only (e.g. vendor) fails; head -n1vendor vendor ✅ fixes #483
origin + others succeeds origin (unchanged) ✅ common case preserved
Multiple non-origin remotes fails; head -n1 → first in .git/config first remote ✅ best-effort

grep -qx (quiet + exact-line) is the right tool — prevents false matches on names like my-origin or not-origin. The "set origin first, then maybe overwrite" pattern makes the origin-as-default intent readable and is fully equivalent to a symmetric if/else.

Fix 2 — Parameter expansion for prefix strip (check/SKILL.md:66, lint/SKILL.md:68):

The old sed "s#^$REMOTE/##" used # as the sed delimiter, which collided with a # anywhere in $REMOTE (a Git-legal character). The replacement ${DEFAULT_BRANCH#"$REMOTE/"} is a bash parameter expansion that strips the shortest prefix match. Quoting "$REMOTE/" inside the expansion suppresses glob interpretation, so remote names containing *, ?, [, #, or any other metacharacter are treated literally. The strip is a no-op on an empty string, so the ls-remote fallback path on the next line is unaffected.


Codex finding (P2) — already addressed

The Codex review flagged the sed delimiter collision against commit 442145c. Commit 23fcdad (fix(toolchain): strip remote prefix via param expansion, not sed delimiter) directly resolves it. The HEAD state is clean with respect to that concern.


Documentation and CHANGELOG

  • Prose in both SKILL.md files accurately describes the new two-step fallback (tracking remote → origin if present → first present remote) and correctly notes the unpushed-branch case is now covered. The old prose claimed git clone -o vendor "still resolves," which only held for pushed branches — fixed.
  • CHANGELOG entry covers both fixes with root cause, old behavior, new behavior, and graceful-degradation note. Follows Keep a Changelog format. ✅
  • Patch bump 0.4.10.4.2 is correct for two bug fixes with no behavior change on the common path. ✅

CLAUDE.md alignment

Criterion Status
Repo-agnostic — no hardcoded remote names ✅ improved
Configurable without editing the plugin ✅ unchanged
Plugin-form-safe
Explicit semver bump
No injection surface — git remote piped to grep/head, never interpolated into shell

Verdict: LGTM. Both fixes are correct and well-scoped. The Codex finding was valid against the first commit and is resolved in the second. No blocking issues.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 23fcdad051

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread plugins/toolchain/skills/check/SKILL.md Outdated
…t the first present one

The clean-working-tree branch-diff fallback in /toolchain:check and
/toolchain:lint picked `git remote | head -n1` when the branch had no
tracking remote and no `origin`. A remote added but never fetched (e.g.
`backup` alongside a `vendor` clone) has no local `refs/remotes/<remote>/*`,
so its `git ls-remote --symref` default-branch query succeeds over the
network but `git rev-parse <remote>/<branch>` fails — the branch diff was
skipped even though a later remote (`vendor/main`) was usable locally.

Both call sites now probe candidate remotes in priority order (tracking
remote, then `origin` if present, then the rest) and select the first whose
default branch resolves to a locally available tracking ref. The common
tracking-remote case still short-circuits on the first candidate with no
extra network calls, and detection still degrades gracefully when no
candidate yields a local default branch.

Verified empirically: the never-fetched-remote case now selects the usable
remote; a pushed feature branch tracking `origin` still selects `origin/<default>`
(not the feature ref, so merge-base is non-empty); no-remote degrades gracefully.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@claude

claude Bot commented Jul 19, 2026

Copy link
Copy Markdown

Claude finished @kyle-sexton's task in 3m 16s —— View job


PR Review: fix(toolchain) — probe remotes for available default-branch ref (all 3 commits)

  • Gather PR context
  • Read current state of skills/check/SKILL.md
  • Read current state of skills/lint/SKILL.md
  • Assess latest commit d64b22c against Codex P2 finding
  • Review shell logic correctness and edge cases
  • Post final review

What changed in d64b22c

The Codex P2 finding flagged that git remote | head -n1 selects by config-insertion order, not usability. A remote added but never fetched (backup alongside a fetched vendor) has no local refs/remotes/backup/*, so git ls-remote can succeed over the network but git rev-parse backup/main fails — the branch diff was skipped even though vendor/main was available locally.

d64b22c replaces the single-remote selection with a probing loop. Both check and lint now use:

REMOTE="" DEFAULT_BRANCH=""
TRACKED=$(git config "branch.$(git branch --show-current | tr -d '\r').remote" 2>/dev/null | tr -d '\r')
[[ "$TRACKED" == "." ]] && TRACKED=""
CANDIDATES=$( { [[ -n "$TRACKED" ]] && echo "$TRACKED"; git remote | grep -qx origin && echo origin; git remote; } | awk 'NF && !seen[$0]++' )
while IFS= read -r CANDIDATE; do
  BRANCH=$(git symbolic-ref --short "refs/remotes/$CANDIDATE/HEAD" 2>/dev/null)
  BRANCH=${BRANCH#"$CANDIDATE/"}
  BRANCH=${BRANCH:-$(git ls-remote --symref "$CANDIDATE" HEAD 2>/dev/null | awk '/^ref:/{sub(/refs\/heads\//,"",$2); print $2; exit}')}
  if [[ -n "$BRANCH" ]] && git rev-parse --verify --quiet "$CANDIDATE/$BRANCH" >/dev/null; then
    REMOTE=$CANDIDATE DEFAULT_BRANCH=$BRANCH
    break
  fi
done <<< "$CANDIDATES"

Shell logic — correctness and edge cases

Priority constructionawk 'NF && !seen[$0]++' deduplicates while preserving order (first-seen wins), giving: tracking remote → origin if present → all remotes in .git/config order. Each remote appears exactly once.

Scenario CANDIDATES order Loop selects Correct?
Tracking remote = origin, only remote origin origin (first try) ✅ common case, no extra calls
Tracking remote = upstream, origin also present upstream, origin upstream (first, if fetchable), else origin ✅ tracking remote priority preserved
Unpushed branch, vendor clone + unfetched backup backup, vendor backup tried, fails rev-parse → vendor selected ✅ Codex P2 fixed
No remotes at all "" (empty) loop body: both probes fail, REMOTE stays "" ✅ graceful degradation
All remotes unfetched each fails rev-parse loop exhausted, REMOTE stays "" ✅ graceful degradation

The guard that makes it correct: git rev-parse --verify --quiet "$CANDIDATE/$BRANCH" — a candidate is only accepted when its default-branch tracking ref exists locally. ls-remote network success without a local ref is explicitly skipped. This is the precise fix the Codex finding required.

Parameter expansion (${BRANCH#"$CANDIDATE/"}) carries forward correctly from 23fcdad — quoting "$CANDIDATE/" inside the expansion suppresses glob interpretation, so remote names with #, *, ?, or other metacharacters are treated literally.

awk 'NF && !seen[$0]++': NF (field count) is zero for empty lines, so they are filtered. The !seen[$0]++ idiom prints first-occurrence lines only. Correct deduplication.

while IFS= read -r ... done <<< "$CANDIDATES": Standard bash idiom. IFS= preserves leading/trailing whitespace. -r prevents backslash interpretation. When CANDIDATES is empty, the herestring feeds one empty line: the loop runs once with CANDIDATE="", both probes fail (guarded by [[ -n "$BRANCH" ]]), and REMOTE stays "". Not a bug — just an extra no-op iteration.

Security: $CANDIDATE originates from git remote (reads .git/config) and is always quoted when passed to git subcommands. No user-controlled external input is interpolated into shell expressions. Clean.


Network-call behaviour

The ls-remote fallback fires when git symbolic-ref refs/remotes/$CANDIDATE/HEAD is absent. For the common pushed-branch case, this symref is set and no network call is made — the commit message claim ("short-circuits on the first candidate with no extra network calls") holds. For a never-fetched candidate, ls-remote does fire, returns a branch name, and then rev-parse rejects it. That network call is unavoidable to determine the default branch name; the current design accepts this as the right trade-off.


Documentation and CHANGELOG

  • Prose in both SKILL.md files accurately describes the three-tier priority and the "accepted only when git rev-parse confirms the tracking ref exists locally" criterion. Clear improvement over the previous text.
  • CHANGELOG entry at d334b5c..5aafd7e covers all three commits (present-remote selection, sed delimiter fix, and the probing loop) in a single [0.4.2] block. Format and content match Keep a Changelog. ✅
  • Version at 0.4.2 (bumped in the first PR commit, not re-bumped in d64b22c — correct). ✅

CLAUDE.md alignment

Criterion Status
Repo-agnostic — no hardcoded remote names ✅ substantially improved across all three commits
Configurable without editing the plugin ✅ unchanged
Plugin-form-safe — no ../ reach-outs
Explicit semver bump 0.4.1 → 0.4.2
No injection surface ✅ all variables quoted; no user-controlled shell interpolation

Minor observation (no action needed)

The [[ -n "$TRACKED" ]] && echo "$TRACKED" construct inside the compound group short-circuits without output when TRACKED is empty — relying on exit code 1 from [[. Inside { }, that exit code is swallowed by the pipeline to awk, so this is correct, just unconventional. Functionally identical to if [[ -n "$TRACKED" ]]; then echo "$TRACKED"; fi.


Verdict: LGTM. All three Codex findings are addressed (P2 sed delimiter collision in 23fcdad, P2 first-remote-not-fetched in d64b22c). The shell logic is correct across every reachable scenario, the common case is unaffected, and degradation is still graceful. No blocking issues.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d64b22c725

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread plugins/toolchain/skills/check/SKILL.md
…emote names

The candidate-remote loop in /toolchain:check and /toolchain:lint passed the
remote name and the `<remote>/<branch>` tracking ref as bare positional
arguments to `git ls-remote`, `git rev-parse`, and `git merge-base`. A
Git-legal remote whose name begins with a dash (e.g. `git clone --origin=-x`)
is then parsed as a command option, so with no `origin` and an unpushed
branch that remote is skipped and the branch diff reported unavailable even
though its `refs/remotes/-x/main` tracking ref exists locally.

Both call sites now verify and diff against the fully-qualified
`refs/remotes/<remote>/<branch>` form (no leading dash exposed, and no
shadowing by a local branch literally named `<remote>/<branch>`) and pass
`--end-of-options` to the `git ls-remote` probe.

Verified empirically against a `git clone --origin=-x` clone: the dash-named
remote is now selected and diffed correctly, and the origin/vendor/no-remote
scenarios are unchanged.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@claude

claude Bot commented Jul 19, 2026

Copy link
Copy Markdown

Claude finished @kyle-sexton's task in 2m 46s —— View job


PR Review: fix(toolchain) — dash-leading remote name safety (commit d726a8b)

  • Gather PR context and identify latest Codex finding
  • Read current state of skills/check/SKILL.md
  • Read current state of skills/lint/SKILL.md
  • Assess whether d726a8b correctly addresses the -- option-terminator issue
  • Post final review

What the Codex P2 finding flagged (against d64b22c)

When the only usable remote is dash-leading (e.g., created by git clone --origin=-x), several git subcommands inside the probing loop would misparse $CANDIDATE as an option flag:

  • git ls-remote --symref "$CANDIDATE" HEAD-x parsed as a flag, not the repository
  • git rev-parse --verify --quiet "$CANDIDATE/$BRANCH"-x/main attempted as option with arg /main
  • git merge-base "$REMOTE/$DEFAULT_BRANCH" HEAD-x/main attempted as option

How d726a8b addresses each surface

The current code in both skills/check/SKILL.md:64–76 and skills/lint/SKILL.md:66–78:

while IFS= read -r CANDIDATE; do
  BRANCH=$(git symbolic-ref --short "refs/remotes/$CANDIDATE/HEAD" 2>/dev/null)
  BRANCH=${BRANCH#"$CANDIDATE/"}
  BRANCH=${BRANCH:-$(git ls-remote --symref --end-of-options "$CANDIDATE" HEAD 2>/dev/null | awk ...)}
  if [[ -n "$BRANCH" ]] && git rev-parse --verify --quiet "refs/remotes/$CANDIDATE/$BRANCH" >/dev/null; then
    REMOTE=$CANDIDATE DEFAULT_BRANCH=$BRANCH
    break
  fi
done <<< "$CANDIDATES"
if [[ -n "$REMOTE" ]]; then
  git diff --name-only "$(git merge-base "refs/remotes/$REMOTE/$DEFAULT_BRANCH" HEAD)..HEAD"
Surface Old form New form Technique Safe?
git symbolic-ref "refs/remotes/$CANDIDATE/HEAD" unchanged refs/ prefix, never parsed as option ✅ was already safe
git ls-remote --symref "$CANDIDATE" HEAD --symref --end-of-options "$CANDIDATE" HEAD --end-of-options terminates option parsing ✅ fixed
git rev-parse "$CANDIDATE/$BRANCH" "refs/remotes/$CANDIDATE/$BRANCH" refs/ prefix, parsed as refspec not option ✅ fixed
git merge-base "$REMOTE/$DEFAULT_BRANCH" "refs/remotes/$REMOTE/$DEFAULT_BRANCH" refs/ prefix, parsed as refspec not option ✅ fixed
${BRANCH#"$CANDIDATE/"} unchanged unchanged bash parameter expansion, no subprocess ✅ never at risk

Two distinct techniques, both correct:

  • For git ls-remote: --end-of-options is the right mechanism. It's part of git's parse_options() infrastructure since git 2.24 (Nov 2019). Any command using that infrastructure, including ls-remote, picks it up. The flag order --symref --end-of-options "$CANDIDATE" HEAD is correct — --symref is parsed as an option first, then --end-of-options terminates option processing, then "$CANDIDATE" is taken as the repository positional.

  • For git rev-parse and git merge-base: using the fully-qualified refs/remotes/$CANDIDATE/$BRANCH path is strictly better than --, because refs/... strings are never parsed as option flags regardless of their content. This also removes the need for a local branch named after the default branch to exist — only the remote-tracking ref need be present.

The approach the Codex finding suggested ("add --") would have worked too, but the chosen alternatives are cleaner: the refs/-prefix approach requires no separator at all, and --end-of-options is the git-standard way to do what Codex asked for.

No remaining unsafe surfaces

Tracing the full data flow for a dash-leading $CANDIDATE = "-x", $BRANCH = "main", $REMOTE = "-x", $DEFAULT_BRANCH = "main":

  1. git symbolic-ref --short "refs/remotes/-x/HEAD"-x is inside the string, not a positional ✅
  2. git ls-remote --symref --end-of-options "-x" HEAD--end-of-options guards the positional ✅
  3. git rev-parse --verify --quiet "refs/remotes/-x/main"refs/ prefix guards ✅
  4. git merge-base "refs/remotes/-x/main" HEADrefs/ prefix guards ✅
  5. ${BRANCH#"-x/"} → parameter expansion only ✅

All four git-calling surfaces are addressed. No remaining unsafe path.

Edge cases — unchanged behavior

State Result Correct?
No remotes loop body: both probes fail, REMOTE="", branch-diff skipped ✅ graceful
Common case: tracking remote = origin first candidate succeeds on local symref, no ls-remote call ✅ common-case unchanged
Pushed branch, non-origin clone tracking remote tried first, succeeds ✅ unchanged
Only dash-leading remote, never fetched ls-remote succeeds (network), rev-parse fails (no local ref) → loop exhausted, REMOTE="" ✅ graceful
Only dash-leading remote, fetched ls-remote + rev-parse both succeed, REMOTE="-x" ✅ now works — Codex finding fixed

Minor observations (no action needed)

git ls-remote --end-of-options minimum git version. The --end-of-options flag is part of git's parse_options() infrastructure since 2.24.0 (Nov 2019). Any git ≥ 2.24 supports it in all commands using that infrastructure. This covers all currently-maintained Linux, macOS, and Windows environments. Not a practical concern in 2026, but worth knowing the floor.

Prose accuracy. The explanatory paragraph in both files (check/SKILL.md:80, lint/SKILL.md:82) explicitly calls out both techniques and names the git clone --origin=-x scenario. Clear and correct.

Duplication across check/lint. Both blocks are byte-for-byte identical. Still appropriate for a targeted bug fix; the cross-plugin shared helper remains the tracked follow-up (#436/#442).


Verdict: LGTM. All three Codex findings across the PR's four commits are now fully addressed. The dash-leading-name fix in d726a8b correctly defends every git-calling surface using the right technique for each. No blocking issues.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

toolchain: default-branch detection — non-origin clone with unpushed branch falls back to possibly-absent origin

1 participant