Skip to content

fix(git-shed): harden branch cleanup and add --gone-only - #75

Merged
michen00 merged 16 commits into
mainfrom
fix/git-shed-hardening
May 21, 2026
Merged

fix(git-shed): harden branch cleanup and add --gone-only#75
michen00 merged 16 commits into
mainfrom
fix/git-shed-hardening

Conversation

@michen00

@michen00 michen00 commented May 15, 2026

Copy link
Copy Markdown
Owner

Summary

git-shed failed when listing merged branches checked out in another worktree (+ prefix): the delete loop tried git branch -d '+'. It also failed when deleting a merged branch still active in a linked worktree. Stale upstream parsing mis-read + lines the same way.

This PR hardens cleanup end-to-end:

  • Merged detection: git for-each-ref --merged against origin/TARGET_BRANCH (fallback: local target) instead of parsing git branch --merged lines
  • Worktrees: remove linked worktrees before git branch -d / -D, using git worktree list --porcelain (paths with whitespace / multiple matches), with --force --force retry on lock errors; skip branch on failure instead of aborting
  • Stale / gone: parse [gone] with both * and + prefixes in git branch -vv
  • Visibility: print git branch -v and git worktree list before cleanup
  • --gone-only: skip the merged pass and only delete upstream-deleted branches (former clean_gone scope)

Closes #82 (review-convergence bulletin).

Test plan

  • SCRIPTS=git-shed PARALLEL=false ./tests/run-tests.sh (8/8)
  • Manual: git-shed --dry-run on a repo with merged and [gone] branches
  • Manual: git-shed --gone-only --dry-run skips merged listing

List merged locals via for-each-ref, fix stale parsing for + worktree
lines, remove linked worktrees before branch delete, and tolerate
per-branch delete failures.
@qodo-code-review

Copy link
Copy Markdown

Review Summary by Qodo

Harden git-shed merged branch cleanup and worktree handling

🐞 Bug fix ✨ Enhancement

Grey Divider

Walkthroughs

Description
• Replace branch parsing with git for-each-ref to avoid malformed tokens
• Add linked worktree removal before branch deletion operations
• Fix stale branch detection to handle + prefix like * prefix
• Implement per-branch error tolerance with graceful failure handling
Diagram
flowchart LR
  A["Branch Detection"] -->|for-each-ref| B["Clean Branch Names"]
  B -->|Check Worktrees| C["Remove Linked Worktrees"]
  C -->|git branch -d/-D| D["Delete Branches"]
  D -->|Error Handling| E["Continue on Failures"]
  F["Stale Detection"] -->|Handle + prefix| G["Fixed Parsing"]
Loading

Grey Divider

File Changes

1. git-shed 🐞 Bug fix +52/-19

Robust branch cleanup with worktree and error handling

• Replace git branch --merged with git for-each-ref to extract clean branch names without line
 prefixes
• Add remove_linked_worktree_for_branch() function to remove worktrees before branch deletion
• Update stale branch detection to treat + prefix (linked worktree) same as * prefix (current
 branch)
• Implement error handling to skip branches on deletion failure instead of aborting entire operation
• Update documentation to mention linked worktree removal during merged branch cleanup

git-shed


Grey Divider

Qodo Logo

@qodo-code-review

qodo-code-review Bot commented May 15, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (1) 📘 Rule violations (0) 📎 Requirement gaps (0)

Grey Divider


Action required

1. Detached HEAD skips cleanup ✓ Resolved 🐞 Bug ≡ Correctness
Description
delete_merged_branches always passes current_branch as a grep -e pattern; when HEAD is
detached, git branch --show-current is empty, so grep -v -e "" filters out every branch and the
script reports no merged branches. This silently disables merged-branch cleanup in detached-HEAD
contexts (common in CI/rebase/bisect workflows).
Code

git-shed[R146-153]

Evidence
git-shed unconditionally uses current_branch as a grep pattern when filtering merged branches;
the repo’s mergewith script explicitly notes that git branch --show-current returns an empty
string in detached HEAD state, which would make the grep pattern empty and invert-filter
everything.

git-shed[144-154]
mergewith[66-75]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`delete_merged_branches()` builds `merged_branches` by piping refs into `grep -vxF -e "$TARGET_BRANCH" -e "$current_branch"`. When `current_branch` is empty (detached HEAD), `grep -e ""` matches every line, and with `-v` it removes every line, producing an empty merged set.

### Issue Context
The repo already documents that `git branch --show-current` returns an empty string in detached HEAD state; `git-shed` should not treat that as a filter pattern.

### Fix Focus Areas
- git-shed[146-153]

### Suggested fix
Only exclude `$current_branch` when it is non-empty, e.g.:

```bash
current_branch=$(git branch --show-current)
if [[ -n "$current_branch" ]]; then
 merged_branches=$(git for-each-ref ... | grep -vxF -e "$TARGET_BRANCH" -e "$current_branch" || true)
else
 merged_branches=$(git for-each-ref ... | grep -vxF -e "$TARGET_BRANCH" || true)
fi
```

(Or replace the `grep` with a small `awk` filter that checks `current_branch` conditionally.)

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Worktree path parsing broken ✓ Resolved 🐞 Bug ☼ Reliability
Description
remove_linked_worktree_for_branch parses git worktree list via awk '{print $1}', which
truncates worktree paths containing whitespace and can also yield multiple newline-separated paths;
the result is then passed as a single path to git worktree remove. This can fail to remove the
linked worktree (or target the wrong directory), leaving merged/stale branches undeletable and
undermining the script’s new hardening behavior.
Code

git-shed[R75-90]

Evidence
The script derives the worktree path by taking the first whitespace-delimited token from `git
worktree list output and then passes that value as the sole path argument to git worktree remove`;
this directly couples whitespace-sensitive parsing to a destructive filesystem operation.

git-shed[71-92]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`remove_linked_worktree_for_branch` currently identifies a linked worktree by running `git worktree list | ... | awk '{print $1}'`. This is not safe because the first whitespace-delimited field is not a robust way to capture a filesystem path (paths may contain spaces), and multiple matches will be collapsed into a single multi-line variable that is then passed as one argument to `git worktree remove`.

## Issue Context
The function’s output (`$worktree`) is used directly in `git worktree remove --force "$worktree"`, so any truncation or multi-line value can cause failure to remove the correct linked worktree, preventing subsequent `git branch -d/-D` from succeeding.

## Fix Focus Areas
- git-shed[71-92]

## Suggested fix (high level)
- Replace `git worktree list` parsing with `git worktree list --porcelain` and extract `worktree` entries whose `branch` matches `refs/heads/$branch`.
- Support multiple matches by iterating (e.g., `while IFS= read -r wt; do ...; done`).
- Keep paths intact (no whitespace splitting); avoid `awk '{print $1}'`/`cut -d' '`.

Example approach:
```bash
worktrees=$(git worktree list --porcelain | awk -v br="refs/heads/$branch" '
 $1=="worktree" { wt=$2 }
 $1=="branch" && $2==br { print wt }
')

while IFS= read -r wt; do
 [[ -z "$wt" ]] && continue
 ... git worktree remove ...
done <<< "$worktrees"
```

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

3. Detached-HEAD test false-positive 🐞 Bug ≡ Correctness
Description
The new detached-HEAD test asserts only that output contains "feature-branch", but git-shed prints
all local branches via git branch -v before merged-branch selection, so the test can pass even if
merged-branch detection is broken. This weak assertion can mask regressions in the
detached-HEAD-specific filtering logic.
Code

tests/git-shed.bats[R96-114]

Evidence
The test’s assert_output_contains "feature-branch" can be satisfied by the script’s unconditional
"Local branches" listing, so it does not validate the merged-branch selection path under detached
HEAD.

tests/git-shed.bats[96-114]
git-shed[88-94]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
The detached-HEAD test currently checks for `feature-branch` anywhere in output, but `git-shed` prints `git branch -v` unconditionally, which already includes `feature-branch`. This makes the test non-diagnostic for the intended behavior (merged-branch detection in detached HEAD).

### Issue Context
`git-shed` prints a full local-branch listing before it computes/prints the merged-branch candidates, so substring checks can easily match the prelude instead of the merged-branch section.

### Fix Focus Areas
- tests/git-shed.bats[96-114]

### Suggested fix
Update the assertion to match output that only appears when the merged-branch pass actually ran for that branch, e.g.:
- `assert_output_contains "Processing merged branch: feature-branch"`, or
- `assert_output_contains "[DRY-RUN] Would delete merged branch: feature-branch"`, or
- assert both `"Branches fully merged into"` and that `feature-branch` appears after that marker (more strict parsing).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


4. Dash branch name rejected ✓ Resolved 🐞 Bug ≡ Correctness
Description
The new -*) case rejects any dash-prefixed positional argument, so a valid TARGET_BRANCH like
-wip will always error as an “Unknown option” instead of being treated as the branch name. This
makes git-shed unusable for repos that use dash-prefixed branch naming conventions unless branches
are renamed.
Code

git-shed[R51-55]

Evidence
The argument parser matches -*) before the positional *) handler, so any dash-prefixed token
will never be assigned to TARGET_BRANCH and instead triggers the hard error path.

git-shed[33-59]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`git-shed` now treats any argument starting with `-` as an option and errors out (`-*) ... exit 2`). Because the script does not support the conventional `--` end-of-options marker, it is impossible to pass a dash-prefixed branch name as the positional `TARGET_BRANCH`.

### Issue Context
This regression was introduced by adding the `-*)` unknown-option handler.

### Fix Focus Areas
- Add support for `--` in the argument parser (stop option parsing and treat remaining args as positionals).
- Alternatively, allow exactly one positional `TARGET_BRANCH` and treat subsequent `-*` tokens as positional once `TARGET_BRANCH` is already set.

- git-shed[47-59]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


5. Bare repo hard-fails ✓ Resolved 🐞 Bug ☼ Reliability
Description
remove_linked_worktree_for_branch unconditionally runs git rev-parse --show-toplevel (and `git
worktree list --porcelain`), which fails in a bare repo / no-worktree context and—because the script
is set -e—terminates git-shed before it can proceed with cleanup.
Code

git-shed[R77-82]

Evidence
The script enables set -euo pipefail, so a failing command substitution/assignment causes an
immediate exit. The new function performs git rev-parse --show-toplevel and `git worktree list
--porcelain` without any guard/fallback, so running in a bare repo (or any no-worktree context) will
abort execution.

git-shed[3-3]
git-shed[75-82]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`remove_linked_worktree_for_branch()` runs `git rev-parse --show-toplevel` and `git worktree list --porcelain` unconditionally. In a bare repository (or any environment without an attached worktree), these commands exit non-zero and, due to `set -euo pipefail`, the entire script exits.

### Issue Context
The worktree removal is only needed when a working tree exists; in bare repos there are no worktrees to remove.

### Fix Focus Areas
- git-shed[77-82]

### Implementation notes
- Add a guard at the start of `remove_linked_worktree_for_branch()`:
 - If `git rev-parse --is-bare-repository` is true, `return 0`.
 - Optionally also guard with `git rev-parse --is-inside-work-tree` to handle other no-worktree contexts.
- Alternatively, make `toplevel=$(git rev-parse --show-toplevel ...)` non-fatal and treat an empty `toplevel` as "skip worktree removal".

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request enhances the git-shed script by adding the ability to automatically remove linked worktrees when deleting merged or stale branches. It introduces a helper function to identify and force-remove worktrees and refactors branch identification to use git for-each-ref for better reliability. Review feedback suggests improving the worktree detection logic to handle paths with spaces using the --porcelain flag and adding --no-color to git branch commands to prevent ANSI escape codes from breaking the parsing logic.

Comment thread git-shed Outdated
Comment thread git-shed Outdated
Comment thread git-shed Outdated
michen00 and others added 4 commits May 20, 2026 02:50
`remove_linked_worktree_for_branch` parsed `git worktree list` via
`awk '{print $1}'`, which truncates paths containing whitespace
(common on macOS / Windows) and collapses multiple matches into a
single multi-line value that was then passed as one argument to
`git worktree remove`. The `grep -F "[$branch]"` heuristic also
false-positives on bracketed path components.

Switch to `git worktree list --porcelain` and match by full ref name
(`refs/heads/$branch`), iterating distinct paths so multi-match
cases are handled correctly and whitespace is preserved.

Addresses Qodo (#75 r3250784699) and Gemini (#75 r3250779444).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@michen00

Copy link
Copy Markdown
Owner Author

/agentic_review

@qodo-code-review

qodo-code-review Bot commented May 20, 2026

Copy link
Copy Markdown

Persistent review updated to latest commit 3c0a185

@michen00

Copy link
Copy Markdown
Owner Author

[Review-Convergence] Round 1: active

  • Head: 3c0a185
  • Base ref: main
  • CI: passing (9/9 required checks green on the new head)
  • Bot threads: 0 unresolved
  • Human threads: 0 unresolved
  • Ineligible-reviewer threads: 2 unresolved (Gemini r3250779444 outdated/addressed; Gemini r3250779451 needs-discussion --no-color hardening, deferred to author)
  • Clean signals: none yet on the current head
  • Pending reviewers: Qodo (re-requested at 2026-05-20T02:21:30Z, trigger comment 4493941993)
  • Engaged reviewer set: {qodo}; Gemini ineligible per skill adapter table; Copilot and Codex not engaged on this PR
  • Catch-up: none (base is ancestor of PR head)
  • Remediation: commit 3c0a185 replaces git worktree list | awk '{print $1}' with porcelain parsing matched by full ref; addresses Qodo r3250784699 + Gemini r3250779444 (same root cause)
  • Next action: wait for Qodo re-review notification on 3c0a185
  • Bulletin: Review convergence: PR #75 #82

Cross-repo adaptations (bin, not ai4inputs-specs): no pr-catchup workflow (update-branch only); Gemini ineligible per skill; Copilot/Codex never first-requested; CLEAN_SIGNAL_TARGET=2 likely unreachable from {qodo} alone. Single-round invocation — no /loop scheduled.

Bundle porcelain worktree removal with optional upstream-only cleanup,
branch/worktree status output before deletes, and bats coverage for
--gone-only.
@michen00 michen00 changed the title fix(git-shed): harden merged branch cleanup fix(git-shed): harden branch cleanup and add --gone-only May 20, 2026
@michen00

Copy link
Copy Markdown
Owner Author

[Review-Convergence] Round 2: blocked

  • Head: e8c1a2e
  • Base ref: main
  • CI: failing (Run CI ubuntu-latest + pre-commit.ci; shellcheck SC2001 on git-shed lines 134 and 173, both pre-existing from e8c1a2e)
  • Behind base: no
  • Round outcome: blocked — escalation_reason: ci-failing-pre-existing

Operator policy applied this round:

  • Gemini elevated to eligible reviewer family (slug google, root login gemini-code-assist[bot]). Re-request via /gemini review. Pending detector uses submittedAt only because Gemini reviews return commit_id: null in this repo. Qodo + Gemini is a valid 2-family quorum at CLEAN_SIGNAL_TARGET=2.
  • Operator-authorized --no-color defensive hardening on Gemini thread r3250779451 (the URL r3270864147 in the brief is michen00's reply on that same thread).

Code changes: none pushed this round.

What I did:

  • Pulled e8c1a2e into the worktree (3c0a185..e8c1a2e FF; user's push added --gone-only and pre-cleanup branch listing).
  • Applied signal-propagation rule for the head move: user push touched git-shed (PR-owned scope), but no clean signals existed to invalidate.
  • Prepared the authorized fix: git branch -vv --no-color | awk ... on git-shed line 163. bash -n clean. make test SCRIPTS=git-shed PARALLEL=false passed 8/8.
  • Pre-commit hook refused the commit because shellcheck SC2001 fails on git-shed:134 (echo "$merged_branches" | sed 's/^/ /') and git-shed:173 (echo "$stale_branches" | sed 's/^/ /'). Both lines were introduced by the user's e8c1a2e push, not by the prepared fix.
  • Per skill § Step 8 ("If CI failed and the failure is not confidently attributable to a fix the runner just made, stop with blocked"), unstaged the change, restored the worktree, and stopped without pushing.
  • Reviewer re-requests intentionally withheld: re-firing Qodo or Gemini against a CI-failing head would just produce CI-failure feedback, not the fresh code review convergence needs.

Reviewer state at HEAD (e8c1a2e):

  • Qodo (qodo): last review submitted 2026-05-15T20:20:22Z on 6a50aa7. Persistent-review-updated comment at 02:21:36Z and CI-feedback comment at 02:33:47Z observed, but no fresh review at e8c1a2e. Pending.
  • Gemini (google): last review submitted 2026-05-15T20:19:23Z on 6a50aa7. No fresh review at e8c1a2e. Pending.

Unresolved review threads (paginated, totalCount cross-checked): 1 unresolved.

  • r3250779451 (Gemini --no-color) — outdated since the runner replied at r3270864147, still unresolved. This is the operator-authorized fix target; remediation deferred per the rule above.

Next action: author resolves SC2001 on git-shed lines 134 and 173 (e.g. bash parameter expansion or a while read loop). Once CI is green, the next convergence round can push the prepared --no-color hardening and re-request both Qodo and Gemini for the 2-family quorum.

Anything surprising: the operator-cited URL r3270864147 is the runner's own Round 1 reply on Gemini's root thread r3250779451, not a new thread keyed after the push. Both refer to the same conversation. Also: Run Tests (ubuntu + macOS) is green; the failure is exclusively in the pre-commit / shellcheck stage.

Bulletin updated: #82

michen00 and others added 2 commits May 19, 2026 19:51
Replaces `echo "$var" | sed 's/^/  /'` with bash parameter
expansion. Resolves shellcheck SC2001 on git-shed:134 and :173
that blocked CI after #75 added `--gone-only`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The `[gone]` awk pattern fails when git colorizes output (e.g.
`color.ui=always` in user config). `--no-color` makes the filter
deterministic regardless of terminal/config state.

Addresses gemini-code-assist review thread on PR #75:
#75 (comment)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@michen00

Copy link
Copy Markdown
Owner Author

/gemini review

@michen00

Copy link
Copy Markdown
Owner Author

/agentic_review

@qodo-code-review

qodo-code-review Bot commented May 20, 2026

Copy link
Copy Markdown

Persistent review updated to latest commit 49126d6

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a --gone-only flag to the git-shed script and adds logic to automatically remove linked worktrees before branch deletion. Feedback suggests refining the worktree removal process to prevent accidental data loss by avoiding unconditional use of --force. It also recommends filtering out the current branch from deletion candidates and using LC_ALL=C for more reliable pattern matching in Git output.

Comment thread git-shed Outdated
Comment thread git-shed Outdated
Comment thread git-shed
michen00 added 5 commits May 19, 2026 21:42
The worktree-removal helper jumped straight to `git worktree remove
--force`, which discards any uncommitted changes in the linked worktree
without warning. Try a plain `git worktree remove` first so clean
worktrees succeed safely; escalate to `--force` (dirty/locked) and then
`--force --force` (administrative override) only when the previous step
fails. Preserves uncommitted work by default while keeping the original
cleanup behavior available as a fallback.

Addresses Gemini review thread on git-shed:114.
`delete_merged_branches` could surface HEAD as a candidate when the
current branch is also merged into the target. `git branch -d` always
refuses to delete the current branch, so the script printed a warning
and attempted a worktree-removal pass for a branch that could never be
deleted. Filter the current branch up-front with `git branch
--show-current`; safe in detached HEAD because `for-each-ref` never
emits empty refnames.

Addresses Gemini review thread on git-shed:125.
`delete_gone_branches` matched the `: gone]` token from `git branch -vv`
output and treated the current-branch marker (`*`) the same as the
other-worktree marker (`+`), surfacing HEAD as a deletion candidate
even though `git branch -D` always refuses it. Set `LC_ALL=C` for
defensive locale-independence on the input stream, and split the awk
arm so only `+` rows print field 2 while `*` rows are skipped outright.

Addresses Gemini review thread on git-shed:164.
The `-*)` case in the option parser rejected any dash-prefixed
positional argument, making TARGET_BRANCH names like `-wip` unusable.
Add the conventional `--` end-of-options marker so callers can pass
dash-prefixed branch names (e.g. `git-shed -- -wip`).

Reported by Qodo /agentic_review (issue-comment 4463230561, Bug 2).
`remove_linked_worktree_for_branch` ran `git rev-parse --show-toplevel`
unconditionally, which exits 128 in a bare repository. Under
`set -euo pipefail` that aborts the entire script before merged/gone
branch cleanup completes. Bare repos have no working trees to remove,
so guard the call: skip immediately when the repo is bare or when
`--show-toplevel` is unavailable for any other reason.

Reported by Qodo /agentic_review (issue-comment 4463230561, Bug 3).
@michen00

Copy link
Copy Markdown
Owner Author

[Operator-assisted assessment]

Qodo summary (issue-comment 4463230561)

Bug 1: Worktree path parsing broken — already resolved

Confirmed resolved. Current remove_linked_worktree_for_branch parses git worktree list --porcelain (matching branch refs/heads/$branch records) and iterates via while IFS= read -r, so paths with whitespace and multiple linked worktrees are handled correctly. Landed in 3c0a185 and refined in e34712f.

Bug 2: Dash branch name rejected — fixed

  • Investigation: Reproduced at 2d0067f. The -*) arm in the option parser caught any dash-prefixed positional before *) could assign it to TARGET_BRANCH, so git-shed -wip errored with Unknown option: -wip and exit 2. Real bug; users can legitimately name branches -wip, -rc1, etc. (refusable via git branch but creatable via git update-ref or imported from another remote).
  • Determination: Fix. Added support for the conventional -- end-of-options sentinel. Anything after -- is treated as positional TARGET_BRANCH. Updated HELP text. Added two regression tests (-- accepts dash-prefixed target branch, rejects dash-prefixed target without --).
  • Commit: 9e3c73f fix(git-shed): support -- for dash branch names

Bug 3: Bare repo hard-fails — fixed

  • Investigation: Reproduced at 2d0067f. In a bare repo, git rev-parse --show-toplevel exits 128 (fatal: this operation must be run in a work tree). Because remove_linked_worktree_for_branch runs that inside an assignment under set -euo pipefail, the entire script aborts before merged/gone-branch cleanup can complete. git worktree list --porcelain, git branch -v, git for-each-ref and git branch -d all work in bare repos, so the only break point is the toplevel call.
  • Determination: Fix. Bare repos by definition have no working trees to remove, so guard with git rev-parse --is-bare-repository; also treat any other --show-toplevel failure (e.g. running outside a repo) as a no-op return rather than a hard abort. Added regression test that clones a populated repo as --bare and runs git-shed --dry-run -y main against it; verified the test fails without the script-side change.
  • Commit: 67f84f6 fix(git-shed): skip worktree scan in bare repos

CI failure on Run Tests (macos-latest)

  • Failing test: not ok 3 en_: copies en dash to clipboard (tests/_mnn.bats:83). Assertion [ "$clipboard_content" = "–" ] fails after en_ exits 0; the pbpaste read-back returns empty.
  • Root cause: macOS pasteboard daemon (pboard) race in CI. The test file already contains a setup_file priming hook (lines 52-61) acknowledging the daemon initialises lazily and races against the first pbcopy from a BATS subprocess; the prime is best-effort and not always sufficient on the cold runner. Unrelated to git-shed or anything in this PR — the en_/em_ test files have not been touched on fix/git-shed-hardening.
  • Classification: Pre-existing flake. Same failure observed earlier on this branch at commit 8a03fbb (job 76872297112) before any of the git-shed hardening commits, and it has alternated pass/fail across subsequent commits (passed at 3c0a185, 4a3e20f, 1934645, 49126d6; failed at 8a03fbb, e8c1a2e, 2d0067f) with no test-side changes between them. The Linux job passes consistently. Main is currently green only because the prime happened to win the race on the last push.
  • Recommendation: Re-run the job to unblock this PR; fix the flake in a separate change. Plausible follow-up: poll pbpaste for up to N ms after en_ exits before asserting, or seed the pasteboard from setup() (per-test) instead of setup_file().

Net change this session

  • 9e3c73f fix(git-shed): support -- for dash branch names (Bug 2)
  • 67f84f6 fix(git-shed): skip worktree scan in bare repos (Bug 3)

Both pushed to fix/git-shed-hardening. Head: 2d0067f67f84f6. Local make test SCRIPTS=git-shed PARALLEL=false passes 11/11 (8 prior + 3 new regression tests).

@michen00

Copy link
Copy Markdown
Owner Author

/gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request enhances the git-shed script by adding a --gone-only option, supporting dash-prefixed branch names, and implementing automatic removal of linked worktrees before branch deletion. The core logic has been refactored into functions for better maintainability, and new tests have been added to verify the changes. Review feedback highlights several opportunities to further harden the script against branch names containing whitespace, specifically by improving awk parsing logic and using while read loops instead of for loops to avoid word splitting issues.

Comment thread git-shed
Comment thread git-shed
Comment thread git-shed Outdated
Comment thread git-shed Outdated
@michen00

Copy link
Copy Markdown
Owner Author

/agentic_review

@michen00 michen00 added the needs-decision Human decision needed before PR can move forward label May 20, 2026
@qodo-code-review

qodo-code-review Bot commented May 20, 2026

Copy link
Copy Markdown

Persistent review updated to latest commit 67f84f6

@michen00

Copy link
Copy Markdown
Owner Author

[Review-Convergence] Round 3: blocked

  • Head: 67f84f6
  • Base ref: main
  • Base: 1526cdb
  • CI: passing
  • Bot threads: 4 unresolved (all gemini-code-assist on current HEAD)
  • Human threads: 0 unresolved
  • Clean signals: none
  • Pending reviewers: Qodo (re-requested at 2026-05-20T06:00:15Z, comment 4495000447)
  • Reviewer state: Gemini findings (4 threads on 67f84f6); Qodo pending (last notification names 49126d6, not current HEAD)
  • Catch-up: none (origin/main 1526cdb is an ancestor of HEAD)
  • Code changes: none this round
  • Re-requests issued: Qodo via /agentic_review
  • Escalation reason: bot-finding-needs-human-judgment
  • Next action: operator decides apply-vs-wontfix on the 4 Gemini whitespace-hardening findings (replies posted on each thread)
  • Bulletin: Review convergence: PR #75 #82

Comment thread git-shed Outdated
michen00 and others added 3 commits May 19, 2026 23:54
Address four Gemini Round 3 review findings on PR #75. Each
affected site parsed or iterated branch/worktree refs in a way
that breaks when a name contains an ASCII space (which
`git check-ref-format` permits).

- `remove_linked_worktree_for_branch`: compare full `branch` line
  via `substr($0, 8)` instead of `$2`, so names with spaces match.
  Addresses comment r3271510527.
- `delete_gone_branches`: source stale branches from
  `git for-each-ref` with a `|` delimiter (mirroring
  `delete_merged_branches`), and exclude `$TARGET_BRANCH`.
  Replaces the fragile `git branch -vv | awk` field-split parse.
  Addresses comment r3271510532.
- `delete_merged_branches`: iterate via `while IFS= read -r branch`
  over a `<<<"$merged_branches"` here-string, with an empty-line
  guard. Addresses comment r3271510538.
- `delete_gone_branches`: same iteration fix for stale_branches.
  Addresses comment r3271510543.

Verified: `bash -n`, `shellcheck`, and
`make test SCRIPTS=git-shed PARALLEL=false` (11/11) all pass.
When HEAD is detached, `git branch --show-current` returns the empty
string. Passing it directly into `grep -vxF -e "$current_branch"` makes
grep match every line (since `-e ""` matches all input), and `-v` then
filters every branch out, silently disabling merged-branch cleanup
in detached-HEAD contexts (common in CI/rebase/bisect workflows).

Only exclude `$current_branch` when it is non-empty. Add a regression
test covering the detached-HEAD path.

Resolves Qodo finding r3271553397.
@michen00

Copy link
Copy Markdown
Owner Author

/agentic_review

@michen00

Copy link
Copy Markdown
Owner Author

/gemini review

@qodo-code-review

qodo-code-review Bot commented May 20, 2026

Copy link
Copy Markdown

Persistent review updated to latest commit 248260b

@michen00 michen00 removed the needs-decision Human decision needed before PR can move forward label May 20, 2026
@michen00

Copy link
Copy Markdown
Owner Author

[Review-Convergence] Round 4: active

  • Head: 248260b
  • Base ref: main
  • Base: 1526cdb
  • CI: pending (4 pass + 4 pending + 1 skipping)
  • Bot threads: 0 unresolved
  • Human threads: 0 unresolved
  • Clean signals: none (current head)
  • Pending reviewers: Qodo (re-requested 20:00:04Z, trigger 4502152080), Gemini (re-requested 20:00:06Z, trigger 4502152527)
  • Catch-up: none (origin/main 1526cdb is an ancestor of 248260b)
  • Code changes this round: 248260b fix(git-shed): guard empty current_branch filter — resolves Qodo r3271553397 (detached-HEAD merged-branch filter regression). Added regression test; git-shed bats 12/12.
  • Next action: wait for Qodo + Gemini to complete on 248260b; rerun convergence for 2-family clean quorum
  • Bulletin: Review convergence: PR #75 #82

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request refactors the git-shed script to improve modularity and robustness. Key changes include the addition of a --gone-only flag to target branches with missing upstreams, support for dash-prefixed branch names via the -- separator, and automatic removal of linked worktrees before branch deletion. The logic has been modularized into functions, and handling for bare repositories and detached HEAD states has been improved. Review feedback suggests simplifying the branch exclusion logic using arrays and improving compatibility with older Git versions by replacing git branch --show-current with git symbolic-ref.

Comment thread git-shed
Comment thread git-shed
@michen00

Copy link
Copy Markdown
Owner Author

[Review-Convergence] Round 5: blocked

  • Head: 248260b
  • Base ref: main
  • Base: 1526cdb
  • CI: passing (9 green, 1 skip; macOS pasteboard flake did not surface)
  • Bot threads: 2 unresolved (Gemini r3276842694, r3276842705)
  • Human threads: 0 unresolved
  • Clean signals: Qodo (qodo)
  • Pending reviewers: none — both returned
  • Reviewer state: Qodo clean at 248260b; Gemini findings (2 medium-priority style/portability suggestions on the Round 4 fix site, not correctness)
  • Catch-up: none
  • Escalation: bot-finding-needs-human-judgment
  • Next action: operator decides apply-vs-wontfix on r3276842694 (array-based exclude_patterns refactor) and r3276842705 (git symbolic-ref portability); rerun convergence after resolution. Posted NEEDS-DISCUSSION reply on the first thread (3277018989); the second thread reply was blocked by the harness — operator may post it manually if desired.
  • Re-requests issued this round: none (Round 4 triggers fresh; both reviewers already returned)
  • Bulletin: Review convergence: PR #75 #82

@michen00

Copy link
Copy Markdown
Owner Author

[Review-Convergence] Round 6: converged

  • Head: 248260b
  • Base ref: main
  • Base: 1526cdb
  • CI: passing (9 pass + 1 skipping; zero pending/failing)
  • Bot threads: 0 unresolved (13 total — operator WONTFIX+resolved both Round 5 Gemini findings r3276842694 and r3276842705)
  • Human threads: 0 unresolved
  • Clean signals: Qodo (qodo) @ 248260b, Gemini (google) @ 248260b — 2/2 family-pair quorum satisfied
  • Pending reviewers: none
  • Re-requests this round: none (Round 5 triggers fresh; signals re-derivable from existing reviews + just-resolved thread state)
  • Code changes this round: none
  • Catch-up: none (origin/main 1526cdb is ancestor of 248260b at both Step 4 and Step 9)
  • Step 10 re-fetch discipline: head/CI/threads/reviewers/ancestry all stable across the round; no stale-snapshot abort.
  • Note on mergeStateStatus: BLOCKED + reviewDecision REVIEW_REQUIRED is a soft signal — main is NOT branch-protected, no human threads, not a draft. No human gates remain.
  • Next action: bot-automerge will run on the next PR event.
  • Bulletin: Review convergence: PR #75 #82

@michen00
michen00 merged commit 91a978f into main May 21, 2026
11 checks passed
@michen00
michen00 deleted the fix/git-shed-hardening branch May 21, 2026 00:36
Repository owner deleted a comment from chatgpt-codex-connector Bot Aug 5, 2026
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.

Review convergence: PR #75

1 participant