Skip to content

Refuse to report a branch inventory from a shallow clone - #1392

Merged
BigSimmo merged 5 commits into
mainfrom
claude/test-coverage-analysis-2vcd8a
Jul 30, 2026
Merged

Refuse to report a branch inventory from a shallow clone#1392
BigSimmo merged 5 commits into
mainfrom
claude/test-coverage-analysis-2vcd8a

Conversation

@BigSimmo

@BigSimmoBigSimmo commented Jul 30, 2026

Copy link
Copy Markdown
Owner

Summary

Closes ledger #109. Makes npm run sweep:branch-ledger fail closed unless it can prove the clone has complete history, instead of printing a confident, wrong branch inventory.

Every signal the sweep reports — ahead/behind, --cherry-pick patch-uniqueness, and therefore deletionCandidate — is derived from a merge-base. A shallow clone has a grafted root, so those numbers are wrong without erroring: nothing fails, the output is simply fiction. Remote Claude Code sessions clone shallow by default, so this is the normal state, not an edge case.

The decision is three-way, and only one branch proceeds:

"false" -> complete history, sweep runs
"true" -> shallow, refuse (remedy: git fetch --unshallow)
anything else, including "" -> INDETERMINATE, refuse (remedy: use a real git checkout)

The measured failure this prevents. In a real --depth 1 clone of this repo, the unguarded sweep exits 0 and names the live checked-out branch as a deletion candidate with "no unique patch content":

 0 0 0 no claude/test-coverage-analysis-2vcd8a
1 deletion candidate(s) (no unique patch content — squash-merged or empty):
- claude/test-coverage-analysis-2vcd8a
EXIT=0

A green run recommending deletion of an active branch is the hazard. Guarded, the same clone refuses and exits 1, printing no inventory at all in either text or --json mode — a partial inventory is what invites someone to act on it.

This is not hypothetical. On 2026-07-29 a session swept this repo with 74 of 2829 commits present and reported 90 of 91 branches as carrying unmerged work, while a stale local main read as ahead 52 with refusing to merge unrelated histories. Acting on those numbers meant either deleting live branches or abandoning cleanup as impossible.

The indeterminate case was a review finding, not my original design. The first revision checked !== "true", which treated an unverifiable result as verified complete. tryGit swallows every error into "", so on a genuinely shallow clone where git rev-parse --is-shallow-repository failed for any reason, the sweep would have emitted exactly the inventory this guard exists to prevent — #109 in a different guise. CodeRabbit (Major) and Codex (P2) flagged it independently; both are resolved, and one of my own tests asserted the wrong behaviour and has been replaced, with the reason recorded in the test body.

shallowCloneRefusal takes the raw stdout rather than a boolean so the coercion is under test in both directions: "false" is a truthy string, so comparing on truthiness would refuse on every healthy clone — the symmetric failure, which has its own test.

docs/branch-cleanup-guide.md gains the precondition ahead of its numbered Safety Rules, because the raw git commands the guide documents have no such protection even once the script is guarded.

Verification

  • npm run verify:cheap — exit 0. Test Files 432 passed (432), Tests 4455 passed | 4 skipped (4459).
  • Guard exercised end-to-end in a real --depth 1 clone: refusal on stdout, EXIT=1, no inventory printed; --json emits {"error":"history-not-verified", …, "branches":null} and also exits 1.
  • Non-regression on this full clone: npm run sweep:branch-ledger -- --no-fetch still prints the normal inventory and exits 0.
  • Counterfactual captured by running the pre-change script in the same shallow clone (exit 0, live branch named as a deletion candidate).
  • 5 cases in tests/repo-hygiene.test.ts: refusal content (remedy, #109, merge-base); only an explicit "false" permits the sweep; empty / undefined / unexpected output must refuse; shallow and indeterminate refusals are distinct; whitespace tolerated on both values.
  • npm run docs:check-links and npm run docs:check-scripts — PASS.
  • UI verification not run: no UI, routing, styling or browser-behaviour change.
  • Retrieval/answer evals not run: no retrieval, ranking, selection or answer-generation change.
  • npm run check:production-readiness not run: no clinical workflow, privacy, environment, Supabase, source-governance or deployment change.

Risk and rollout

  • Risk: low, and the change is in the safe direction — it can only stop a report, never produce a wrong one. The realistic failure mode is refusing on a healthy clone; that is guarded by an exact-string match plus a dedicated test, and confirmed by the sweep still running normally here. No product code, schema, API or gate behaviour is touched, and the script remains report-only.
  • Rollback: revert this PR. The sweep returns to its previous behaviour, including the shallow-clone false reporting.
  • Provider or production effects: None. No provider-backed gate was run and no deploy step is involved.

Clinical Governance Preflight

Not applicable to this diff. classifyPullRequestFiles reports clinicalRisk: false — the changed paths are scripts/sweep-branch-ledger.mjs, tests/repo-hygiene.test.ts, docs/branch-cleanup-guide.md and docs/outstanding-issues.md. scripts/** is clinical-risk only when it matches ingest/reindex/migration/governance/production/drift/supabase, and a branch-hygiene reporter matches none of those. No ingestion, answer generation, search/ranking, source rendering, document access, privacy or clinical output behaviour changed.

Notes

#108 remains open and is unaffected: five branches are verified safe to delete, but ref deletion returns HTTP 403 in this environment. Its recorded order is append-the-branch-cleanup-rows-then-delete, because resolveHead rejects a HEAD that is no longer a commit in the repository.

CodeRabbit's Docstring Coverage pre-merge check reports 66.67% against an 80% threshold. That is measured across the whole touched file, not the diff: the two functions added or changed here both carry full JSDoc, and the shortfall is pre-existing helpers (git, tryGit, main). Adding docstrings to untouched functions would widen this PR beyond its subject, so it is left alone deliberately.

Closes ledger #109. Every signal `sweep-branch-ledger` reports — ahead/behind,
`--cherry-pick` patch-uniqueness, and therefore `deletionCandidate` — is derived
from a merge-base. A shallow clone has a grafted root, so those numbers are wrong
WITHOUT erroring: nothing fails, the output is simply fiction.
Measured in a real `--depth 1` clone of this repo: unguarded, the sweep exits 0
and names the live checked-out branch as a deletion candidate with "no unique
patch content". A green run recommending deletion of an active branch is the
hazard, so the guard fails closed — no inventory is printed at all, in either
text or `--json` mode, and the exit code is 1. A partial inventory is what
invites someone to act on it.
`shallowCloneRefusal` takes the raw `git rev-parse --is-shallow-repository`
stdout rather than a boolean so the coercion is covered too: that command prints
the string "false" on a healthy clone, and reading truthiness instead of the
exact value would refuse on every complete repo, which is the one way this guard
could fail dangerously in the opposite direction.
docs/branch-cleanup-guide.md gains the precondition ahead of its numbered Safety
Rules, since the raw git commands it documents have no such protection. Remote
sessions clone shallow by default, so this is the normal state rather than an
edge case.
Verified: npm run verify:cheap exit 0 (432 files, 4454 passed | 4 skipped);
guard exercised end-to-end in a real shallow clone (exit 1, refusal) and on this
full clone (exit 0, normal report).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012YRCXgX4AWZ579bKN6sk6b
@coderabbitai

coderabbitaiBot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your recent review volume is higher than typical usage, so adaptive limits are currently applied.

Next review available in:32 minutes

Your organization has reached its usage spending cap. Adjust your spending cap in the billing tab.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 5a12f603-7d29-4830-afad-6d6bf6a64c13

📥 Commits

Reviewing files that changed from the base of the PR and between 54e80a8 and 6bdabde.

📒 Files selected for processing (7)
  • docs/branch-cleanup-guide.md
  • docs/outstanding-issues.md
  • scripts/reconciliation-preflight.mjs
  • scripts/sweep-branch-ledger.mjs
  • tests/reconciliation-evidence-pack.test.ts
  • tests/reconciliation-preflight.test.ts
  • tests/repo-hygiene.test.ts
📝 Walkthrough

Walkthrough

The branch ledger sweep now fails closed on shallow clones before fetching or comparing branches. It supports plain-text and JSON refusal output, adds behavioral coverage, and documents the full-history precondition and remediation.

Changes

Shallow Clone Safety

Layer / File(s)Summary
Shallow clone refusal and validation
scripts/sweep-branch-ledger.mjs, tests/repo-hygiene.test.ts
The sweep detects confirmed shallow repositories, exits with code 1 without producing branch results, supports text and JSON responses, and tests valid, invalid, absent, and whitespace-padded status values.
Safety documentation and issue status
docs/branch-cleanup-guide.md, docs/outstanding-issues.md
The cleanup guide adds full-history checks and unshallow instructions, while issue 109 records the implemented guard and coverage.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
participant GitRepository
participant SweepBranchLedger
participant CLIOutput
GitRepository->>SweepBranchLedger: report shallow repository status
SweepBranchLedger->>CLIOutput: emit refusal message and exit code 1
Loading

Possibly related PRs

Suggested reviewers:claude

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 66.67% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
Title check✅ PassedThe title clearly and concisely matches the main change: refusing branch inventory reporting from shallow clones.
Description check✅ PassedThe description follows the template well, with Summary, Verification, Risk and rollout, Clinical Governance Preflight, and Notes all addressed.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch

Comment @coderabbitai help to get the list of available commands.

@supabase

supabaseBot commented Jul 30, 2026

Copy link
Copy Markdown

This pull request has been ignored for the connected project sjrfecxgysukkwxsowpy because there are no changes detected in supabase directory. You can change this behaviour in Project Integrations Settings ↗︎.


Preview Branches by Supabase.
Learn more about Supabase Branching ↗︎.

@BigSimmo
BigSimmo marked this pull request as ready for review July 30, 2026 02:35

@coderabbitaicoderabbitaiBot 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@scripts/sweep-branch-ledger.mjs`:
- Around line 115-116: Update shallowCloneRefusal to allow the sweep only when
the normalized shallow-status value is exactly "false"; return distinct
non-empty refusal values for "true" and empty or unexpected output, preserving
the existing normalization. Adjust the related expectations in
tests/repo-hygiene.test.ts to cover complete, shallow, and indeterminate
statuses.
🪄 Autofix (Beta)

❌ Autofix failed (check again to retry)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: d0dcace4-5d4b-46e9-9ae6-9b2fac25a591

📥 Commits

Reviewing files that changed from the base of the PR and between 8dbfc5d and 54e80a8.

📒 Files selected for processing (4)
  • docs/branch-cleanup-guide.md
  • docs/outstanding-issues.md
  • scripts/sweep-branch-ledger.mjs
  • tests/repo-hygiene.test.ts

Comment threadscripts/sweep-branch-ledger.mjs Outdated

@chatgpt-codex-connectorchatgpt-codex-connectorBot 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:54e80a8a35

ℹ️ 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 threadscripts/sweep-branch-ledger.mjs Outdated
@coderabbitai

coderabbitaiBot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Note

Autofix is a beta feature. Expect some limitations and changes as we gather feedback and continue to improve it.

⚠️Branch updated during autofix.

The branch was updated while autofix was in progress. Please try again.

…when shallow
CodeRabbit (Major) and Codex (P2) independently flagged the same hole in the
guard added by the previous commit, and they are right.
`tryGit` swallows every error into "", so the original `!== "true"` check treated
an indeterminate result as proof of a complete clone. If `git rev-parse
--is-shallow-repository` failed for any reason on a genuinely shallow clone, the
sweep would emit exactly the merge-base-derived inventory this guard exists to
prevent — the #109 defect wearing a different hat.
The decision is now three-way, and only one branch proceeds:
"false" -> complete history, sweep runs
"true" -> shallow, refuse
anything else, including "" -> INDETERMINATE, refuse
The two refusals carry different remedies, since one needs `--unshallow` and the
other needs a real git checkout. Whitespace is tolerated in both directions, and
the exact value is compared both ways: "false" is a truthy string, so coercing on
truthiness would refuse on every healthy clone instead. The `--json` error code
becomes `history-not-verified`, which is what the condition actually establishes.
One of my own tests asserted the wrong behaviour — that empty or unexpected
output should permit the sweep. It is replaced, and the reason is recorded in the
test so it is not reintroduced.
Verified: 30 cases in tests/repo-hygiene.test.ts pass; typecheck, eslint and
prettier clean; the sweep still runs normally on this full clone (exit 0).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012YRCXgX4AWZ579bKN6sk6b
@BigSimmo
BigSimmo enabled auto-merge (squash) July 30, 2026 02:43

@chatgpt-codex-connectorchatgpt-codex-connectorBot 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:1b0cbafd23

ℹ️ 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 threaddocs/branch-cleanup-guide.md Outdated
…tate
Codex (P2) found that the guide contradicted itself and that the guard had a
reachable bypass. Both are real.
The precondition block said "check this first, before anything else" but sat
BELOW the paragraph instructing operators to run reconciliation-preflight.mjs.
Worse, that preflight computes its own merge-base-derived ahead/behind at
scripts/reconciliation-preflight.mjs:153 via `git rev-list --left-right --count`
and had no shallow guard, so the documented sequence handed an operator exactly
the unreliable numbers this change exists to prevent, one step before they were
told to check.
Fixed both, not just the ordering: the precondition now precedes the preflight
instruction, and the preflight itself refuses unless history is verified
complete, reusing shallowCloneRefusal. Reordering alone would have left a script
that still reports fiction.
The shared refusal text no longer says "sweep", since two callers now use it.
Caught while verifying: this file's `tryGit` returns { ok, output }, not a bare
string like the sweep's. Passing the object stringified to "[object Object]",
which the guard correctly read as indeterminate and then refused on a HEALTHY
clone — the symmetric failure the guard's own test warns about. Fixed to pass
`.output`, with a note at the call site so the next caller does not repeat it.
Verified: npm run verify:cheap exit 0 (432 files, 4455 passed | 4 skipped);
tests/reconciliation-preflight.test.ts and tests/productivity-workflow.test.ts
pass; both scripts exit 0 on this full clone and exit 1 in a real --depth 1
clone.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012YRCXgX4AWZ579bKN6sk6b

@chatgpt-codex-connectorchatgpt-codex-connectorBot 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:7d57aefb90

ℹ️ 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 threadscripts/reconciliation-preflight.mjs Outdated
BigSimmoand others added 2 commits July 30, 2026 10:58
… not the CLI
The guard sat in the preflight CLI, but the CLI is not the only caller.
buildReconciliationEvidencePack calls collectReconciliationState directly and
stamps the result `status: "complete"`. Proven in a real --depth 1 clone: the
guarded CLI exited 1 while `reconciliation-evidence-pack --output` exited 0 and
wrote a completed pack around merge-base-derived ahead/behind values that a
grafted root makes fiction.
collectReconciliationState now throws UnverifiedHistoryError
(code: "history-not-verified") before resolving the base ref, so every caller —
CLI, evidence pack, and any future one — fails closed by default rather than by
remembering to ask. The CLI catches it only to keep its --json envelope. The
check runs against the passed root, so an injected fixture root is judged on its
own history.
Regression cases live with each entry point and build a real --depth 1 clone
(--depth is ignored for local-path clones, hence the file:// origin), asserting
is-shallow-repository is "true" first so a git behaviour change cannot make them
pass vacuously.
Same shallow clone, before -> after: exit 0 with a `status: "complete"` pack
written -> exit 1, refusal with the --unshallow remedy, no pack written.
Ledger #109.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012YRCXgX4AWZ579bKN6sk6b

@chatgpt-codex-connectorchatgpt-codex-connectorBot 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:6bdabde8cf

ℹ️ 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 threadscripts/sweep-branch-ledger.mjs
@BigSimmo
BigSimmo merged commit bfd764b into mainJul 30, 2026
67 checks passed
@BigSimmo
BigSimmo deleted the claude/test-coverage-analysis-2vcd8a branch July 30, 2026 03:11
Sign up for freeto 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.

2 participants

@BigSimmo@claude