fix(repo-hygiene): git-tree-reset AppliedClean no longer claims success when git clean failed (#395)#590
Conversation
… exit (#395) The --apply path captured git clean -fdx stderr but never checked its exit status, then printed `AppliedClean: git clean -fdx ...` unconditionally — a false success signal when clean errored. Inspect the clean exit code: a non-zero exit not caused by locked/in-use files (already surfaced via Unremovable:) is a genuine failure that emits a FAILED line + `AppliedClean: failed` and exits 7. AppliedReset success is now printed as soon as the reset succeeds — before clean — so a later clean failure still reports the truthful reset outcome. The reparse-point restore guard runs on the failure path too. Adds test coverage (clean-failure via a PATH shim) and bumps 0.3.2 -> 0.3.3. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011V31qpAHP3jfs76B9d5Rfo
|
Claude finished @kyle-sexton's task in 2m 27s —— View job PR Review — fix(repo-hygiene): git-tree-reset AppliedClean false-success
Verdict: Approve with observations. The fix is correct, the exit-code path is clean, and the tests genuinely exercise the failure path. A few minor observations below — none are blocking. Correctness
CLEAN_STDERR="$(git clean -fdx "${PRESERVE_ARGS[@]}" 2>&1 >/dev/null)"
CLEAN_RC=$?The redirect order Failure gate logic ( if [[ "$CLEAN_RC" -ne 0 && "${UNREMOVABLE:-0}" -eq 0 ]]; thenSound: non-zero exit with no locked-file warnings → genuine failure (exit 7). Non-zero exit with locked-file warnings → expected non-fatal case, falls through to the success path. The design intent is clear.
Restore guard on failure path ( Observations (non-blocking)1. Mixed locked + non-locked failure edge case If 2. The success path emits 3. Potential blank stderr line when printf '%s\n' "$CLEAN_STDERR" >&2If git clean exits non-zero but writes nothing to stderr, this emits a bare newline to stderr. Cosmetic, but 4. Test #11 — missing The failure path emits Test shim designThe CLAUDE.md / plugin conventions
Summary: Solid fix. The false-success signal is gone, the failure surface is documented, and the test exercises the path end-to-end with a real git reset. The observations above are all minor; the PR is ready to merge as-is. |
…ard blank stderr Address PR #590 review observations: - #2 (required): emit `Unremovable: 0` before `exit 7` so the apply-path label contract is uniform between the clean success and clean-failure paths. - #3: guard the CLEAN_STDERR echo with `[[ -n ... ]]` so a clean that fails with empty stderr no longer prints a bare newline. - #4: assert RestoredTracked:/Unremovable: labels in the clean-failure test. - #1: one-line comment documenting the deferred mixed locked+unrelated-error fall-through (pre-existing heuristic, not a regression). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011V31qpAHP3jfs76B9d5Rfo
|
Claude finished @kyle-sexton's task in 2m 56s —— View job PR Review — fix(repo-hygiene): git-tree-reset AppliedClean false-success
Verdict: Approve. The two-commit series is correct. Commit Correctness
CLEAN_STDERR="$(git clean -fdx "${PRESERVE_ARGS[@]}" 2>&1 >/dev/null)"
CLEAN_RC=$?Redirect order is correct inside Failure gate (line 250) if [[ "$CLEAN_RC" -ne 0 && "${UNREMOVABLE:-0}" -eq 0 ]]; thenSound: non-zero exit + no locked-file warnings → genuine failure → exit 7. Non-zero exit + locked-file warnings → non-fatal, falls through to success path. The known-limitation comment (mixed locked+non-locked) is present and correctly deferred.
Restore guard runs unconditionally (line 239) — tracked-file recovery runs before the failure gate, so a partially-run clean that deleted tracked files mid-run is still corrected. Correct.
Test case #11Eight assertions (up from the six in the PR body, the
The The shim's dry-run pass-through ( Minor observation (non-blocking)
CLAUDE.md / plugin conventions
Ready to merge. |
Review observation classificationThanks for the thorough review. Verdict was "Approve with observations"; all four are triaged below. Applied in
Verification after the fix: full suite passes ( 🤖 Generated with a Claude Code implementation subagent (issue #395) |
|
Claude finished @kyle-sexton's task in 2m 22s —— View job PR Review — fix(repo-hygiene): git-tree-reset AppliedClean false-success
Verdict: Approve. This is the third pass over this branch; prior rounds (commits Correctness
CLEAN_STDERR="$(git clean -fdx "${PRESERVE_ARGS[@]}" 2>&1 >/dev/null)"
CLEAN_RC=$?Redirect order inside
Failure gate logic (line 250):
Restore guard before the failure gate (line 239): guard runs unconditionally — tracked files deleted mid-run by a partially-executed clean are still recovered on both the success and failure paths. Blank-stderr guard (line 252):
Test case #11Eight assertions cover the full contract of the exit-7 path:
The shim (lines 219–227) intercepts only non-dry-run Residual observation (non-blocking, not new)
CLAUDE.md / plugin conventions
Ready to merge. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 449c04954b
ℹ️ 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".
…ailure path (#605) (#655) ## Summary On the `git-tree-reset.sh` exit-7 clean-failure path (git clean -fdx failed for a non-locked-file cause), the script emitted the machine-readable `RestoredTracked: N` line but skipped the human-visible `WARNING: restored N tracked file(s)` message that the success path already prints when `RESTORED>0`. An operator hitting the failure path could therefore miss that the reparse-point restore guard fired — i.e. that data-loss recovery mutated the working tree back — even though a clean that errors mid-run may have deleted tracked files before failing. ## Fix Mirror the success path's warning onto the exit-7 failure path: under the same `RESTORED>0` condition, emit the same `WARNING: restored N tracked file(s) deleted via reparse-point traversal (junction/symlink into tracked dir).` message (identical wording, same stderr stream) at the point where `RestoredTracked: N` is emitted alone. Both paths now surface restore-guard activity identically for this signal. No other behavior changes. ## Verification Added test case #12 to `git-tree-reset.test.sh`. It drives the exit-7 path with a git shim whose faked `clean` deletes a tracked file (simulating a partial clean that deleted tracked files via reparse-point traversal) and *then* exits non-zero, so `git ls-files --deleted` reports the file and the restore guard recovers it (`RESTORED=1`) on the failure path. It asserts exit 7, `RestoredTracked: 1`, and that the warning now appears. Full suite (50 assertions) passes: ``` PASS: [46] clean failure with restore exits 7 PASS: [47] clean failure emits AppliedClean: failed PASS: [48] clean failure reports a positive RestoredTracked count PASS: [49] clean failure path emits the RESTORED>0 warning (parity with success path) PASS: [50] restore guard recovered the tracked file on the failure path OK: git-tree-reset.sh tests passed ``` Negative check — stashing only the source fix and re-running proves the new test is meaningful (it fails without the fix): ``` FAIL: [49] clean failure path emits the RESTORED>0 warning (parity with success path) — expected WARNING:\ restored\ 1\ tracked\ file\(s\)... in: Upstream: main FAILED: 1 test(s) ``` `shellcheck` on both the script and the test reports no findings. Closes #605 ## Related - #605 — this fix - #395 — family: the `AppliedClean` false-success gate that introduced the exit-7 failure path this warning-parity fix completes - #602 — sibling follow-up from the same #590 merge digest (mixed locked + non-locked clean-failure classifier; out of scope here, left as its own deferred issue) 🤖 Generated with a Claude Code implementation subagent (issue #605) Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Summary
git-tree-reset.sh --applyprintedAppliedClean: git clean -fdx …unconditionally, regardless of whethergit cleanactually succeeded — a false success signal that misleads any operator or automation keying off that line to conclude the tree reached a known-good state.Scope note: the sibling
AppliedResetfalse-success and the unresolvable-@{u}gate that issue #395 references were already closed by #460 (reset-failure aborts clean, exit 5) and #542 (upstream-unresolved block, exit 6). This PR fixes the one remaining unguarded case —AppliedClean— so #395's acceptance criteria are fully met.Fix
git clean -fdx's exit status (CLEAN_RC), read on the line immediately after the capture so$?isn't clobbered (the script runsset -uo pipefail, no-e).AppliedCleansuccess line on the real outcome. A non-zero clean exit whose cause is locked/in-use files is the expected non-fatal case —git cleanreturns non-zero when it fails to remove any path, and that case is already reported honestly viaUnremovable:— so it is not treated as failure (avoids regressing the deliberate locked-file handling on Windows). Only a non-zero exit with nofailed to removewarnings is a genuine failure: it emits an explicitFAILED:line,AppliedClean: failed, and exits 7 instead of a success line.AppliedReset:success line as soon asreset --hardgenuinely succeeds — beforeclean— so a subsequent clean failure still surfaces the truthful reset outcome rather than swallowing it.reset --hardran first).usage(), and thecleanskill'scontext/git-tree-reset.mdgates list.Verification
New test case #11 (
git-tree-reset.test.sh) forces a genuine clean failure via aPATHshim that intercepts onlygit clean(delegating every other subcommand — cruciallyreset— to the real git, so the reset genuinely succeeds). The full suite passes, including the 6 new assertions:(All 43 tests pass; the pre-existing reset-failure suite #22–27 and upstream-unresolved suite #28–37 continue to pass — no regression.
shellcheckis clean on both files.)Live
--applyrun against a repo wheregit cleanfails after a successful reset — the report is now honest and exits non-zero:Before this fix the same run printed
AppliedClean: git clean -fdxand exited0.Closes #395
Related
@{u}, so dry-run success != apply success #396 — sibling issue on the same script (dry-run does not validate@{u}upstream). Not addressed here to avoid a merge collision on the same lines; a separate cycle picks it up.🤖 Generated with a Claude Code implementation subagent (issue #395)