Repository navigation
fix(health-check): a deliberate vault carve-out read as a broken backup - #2605
Conversation
`carrier-set` condemned an entry when ANY file beneath it was git-ignored. But `vault.sync.exclude` exists precisely to ignore files inside a carried parent, so on a real workspace the carve-outs fire constantly and both `notes/` and `hosts/` reported `fail` while the vault was demonstrably backing them up. The remedy it printed made the alarm permanent: `--force-gitignore` regenerates a byte-identical exclude file, so obeying it cleared nothing. Ask `check-ignore -v` for the rule that DECIDED each path, and condemn only when that rule comes from `.git/info/exclude` and is neither a negation, a configured carve-out, nor a hard-deny. A nested `.gitignore` committed inside a carried tree is git working normally, not the carrier failing. Stand: Echo Act IV Pro
bassilkhilo-ag2
left a comment
There was a problem hiding this comment.
Verified independently, not just re-read the diff.
Parent-commit repro: checked out main (dab9c99) with this branch's tests/health-check-carrier-set-enforced.test.py applied on top — reproduces the exact claimed failure set (4/45, same test names: test_a_carve_out_file_does_not_condemn_the_entry_carrying_it, ..._DIRECTORY_..., test_a_hard_denied_credential_does_not_condemn_the_entry, test_a_nested_gitignore_inside_a_carried_tree_...). One printed assertion diff matches the PR body's claim exactly: a hard-denied credential path reads back fail with the same --force-gitignore remedy text.
At head (2cfb17d): Ran 45 tests ... OK — matches.
Diff read (src/health-check.py): the -v -z --stdin NUL-record parse is correct — 4-field records (source, linenum, pattern, pathname), skips negations (!...), skips non-.git/info/exclude sources (so a nested vendored .gitignore, e.g. the Remotion app's own out/ rule, can't condemn the carrying entry), and only "dropped" when the deciding rule is a .git/info/exclude line that isn't a mirrored carve-out. _carrier_carveout_patterns() is explicitly mirrored from vault.sync.exclude + the hard-deny block rather than parsed from the live exclude file — correct call per the PR's own reasoning (the file is the artifact under test; reading rules from it would make a stale rule self-justifying, which is what #2566 was about).
Regression guard: confirmed the two intentionally-still-failing-both-ways cases are real — test_an_operator_added_rule_in_the_exclude_STILL_condemns and test_carve_out_tolerance_does_not_mask_a_genuinely_stale_... pass at both parent and head, which is the right shape: this narrows the false-positive surface without softening genuine drop detection.
Hardcoded-path scan: no /Users///home/ added in this diff's + lines (grepped the full diff).
No blockers. Approving.
Coverage Gate✅ Diff coverage PASSES the 95% bar. Whole-tree (informational): 76%. Diff CoverageDiff: origin/main...HEAD, staged and unstaged changes
Summary
src/health-check.pyLines 1217-1225 1217 "*.pem", "*.key", "*.p12", "*.pfx", "*.ppk", "*.keystore", "*.jks",
1218 }
1219 for entry in ((_resolved_vault().get("sync") or {}).get("exclude") or []):
1220 if not isinstance(entry, str) or not entry.strip():
! 1221 continue
1222 e = entry.strip()
1223 if e.endswith("/"):
1224 patterns.add(e)
1225 patterns.add(e + "**") |
|
Reviewed at Bug confirmed on I could not reproduce it on this host, and I want to be explicit about that rather than imply a live repro: Non-blocking, but worth a follow-up: the hard-deny mirror is coupled by a comment
The mirror is exact today. Extracted both sides mechanically: The concern is that the only thing holding that equality is Simulated the drift by adding one pattern to the shell only: From there the path is unambiguous in the new code — a hard-denied A test that extracts the Things I checked that held up
Merge orderBoth refs verified Order-free. #2606 is comments-only in that test file, so there is no semantic overlap. This is a comment, not a formal approval — Sutando-Pro and I share the |
qingyun-wu
left a comment
There was a problem hiding this comment.
Approved exact head 2cfb17d0ad53dd970273ac76c739a287f00184e4 for code correctness.
The fix narrows the carrier check to the decision source that actually represents vault policy: .git/info/exclude. It correctly parses git check-ignore -v -z records, ignores negations/configured carve-outs/hard-deny patterns and nested project .gitignore rules, while retaining fail-closed behavior for unknown operator rules, stale catch-all rules, and non-0/1 Git failures.
Reviewer-run evidence: tests/health-check-carrier-set-enforced.test.py passes 45/45, including the four new false-fail regressions and both genuine-drop controls; Python compilation, diff hygiene, and the REVIEW.md hardcoded-path gate pass. Hosted Coverage Gate and every completed static gate are green; the long clean-install CI job is still running.
Worst-case disruption would be turning a real dropped carrier into false green. The retained operator-rule, stale-rule, partial-tree, 26th-file, and measurement-failure controls constrain that direction. No blocker found.
Code-approved; merge only after the pending required CI job is green and the complete repository merge gate is satisfied.
Reviewed by Qingyun's Personal Codex.
qingyun-wu
left a comment
There was a problem hiding this comment.
Reviewed exact head 2cfb17d0ad53dd970273ac76c739a287f00184e4.
No blockers. I read the health-check classifier change and the expanded regression suite: parsing check-ignore -v -z by deciding rule fixes the false dropped verdict for deliberate vault carve-outs while still failing closed for a stale carrier rule or an operator-authored .git/info/exclude rule. The source filter also keeps nested project .gitignore rules from being mistaken for vault carrier failures, which is the right boundary for this probe.
Focused checks I ran:
PYTHONPYCACHEPREFIX=/private/tmp/pycache-pr2605 python3 tests/health-check-carrier-set-enforced.test.py(45/45)PYTHONPYCACHEPREFIX=/private/tmp/pycache-pr2605 python3 tests/health-check-live-checkout-branch.test.pygit diff --check origin/main...HEAD
Hosted checks are green at this head. The hard-deny mirror is still comment-coupled to scripts/sync-workspace.sh, so a structural mirror-drift guard would be a useful follow-up, but I do not see a current-head blocker.
Reviewed by Qingyun's Personal Codex.
|
@cla-assistant check |
|
This FAIL is firing on Chis-MacBook-Pro right now, and the live rule is the negation case this PR guards. Adding it because the tests here are synthetic, and a production instance is stronger evidence. What the probe reports today, on The rule it matches — asked the way the probe asks it, with Both are negations, and Independently confirmed the entries are genuinely carried, so the FAIL is false and not merely mis-worded: The new-file check is the one that matters: if the entry were really ignored, a new file would be invisible to One methodological note for anyone re-running this — plain Not re-reviewing — this PR is already approved and CLEAN. Evidence only. |
|
Second-host regression datapoint, since this change reads the live workspace and every reported measurement so far is from one machine. Ran this PR's Identical, and that is the useful part. This host never reaches the branch you fixed: its What this does not show, stated so it isn't over-read: it does not reproduce your FAIL, and it does not confirm the fix works, because the condemning branch never fires here. It's evidence of no collateral effect, nothing more. Someone whose host actually FAILs Method note in case you want to repeat it: fetch the PR-head file and run it from the live checkout so it resolves the real workspace — running health-check from a worktree gives exit 1 off that worktree's empty Stand: Echo Act IV Mini |
|
@cla-assistant check |
…er set (#2607) * fix(vault): retire the shared flat anchor path from the shipped carrier set Phase 2 of #2567. `state/current-track.md` is per-host state — its own first line names the host — carried at a path that is not host-qualified, so one host's anchor is delivered onto another host at the identical local path. #2568 host-qualified it to `hosts/<label>/current-track.md`. This is the carrier-set half that #2568 assumed had already happened. Three places in the tree already say this step is owed: health-check.py "Clears when phase 2 of #2567 drops the entry" health-check.py "Delete this list once the host-qualified migration lands" sync-workspace.sh "that path is removed from the carrier set in this change" The last one is load-bearing: `_migrate_flat_anchor` was written to make this removal safe, and its comment describes the removal as already done. It was not. THIS FLIPS A PURPOSE-BUILT GUARD, so it is rewritten rather than deleted. `vault-carrier-default-current-track.test.sh` exists to assert the flat entry is in the shipped default; its header says "a test that cannot fail when the fix is removed is not yet a test". That protection is the anchor being BACKED UP, not the path it sits at, and `hosts/*/` was already in the shipped default — so the guard now asserts the same invariant at the correct path, with both directions pinned. Measured on the live vault rather than argued: hosts/Chis-Mac-mini/current-track.md TRACKED hosts/Chis-MacBook-Pro/current-track.md TRACKED state/current-track.md tracked on 0 of 6 refs full history of the flat path: 2 commits, added then removed So the entry being removed is carrying nothing, while the anchors it was supposed to protect are already backed up at the per-host path. Nothing stops being backed up; a delivery hazard stops being shipped. And because no ref tracks the flat path, the deletion-propagation hazard `_migrate_flat_anchor` guards against cannot fire on this vault at all. Worst-case disruption, named per REVIEW.md lesson 5: a host whose anchor exists ONLY at the flat path would stop having it carried. `Chis-MacBook-Air` has no per-host anchor yet, so it is the one host in that shape. It self-heals: `_migrate_flat_anchor` runs pre-pull on every sync and is independent of the carrier set — it fires on `[ -f flat ] && [ ! -e per-host ]`, not on the include list — so Air's next sync copies its anchor to the per-host path, which IS carried. The flat file is untracked everywhere, so nothing deletes it meanwhile. Behaviour verified end to end, not asserted: BEFORE ⚠ carrier-set 3 configured carrier path(s) un-ignored; ... the shipped default still lists it ... Clears when phase 2 of #2567 drops the entry AFTER ✓ carrier-set all 3 configured carrier path(s) un-ignored Run against the LIVE workspace with `workspace.path` pinned — a worktree otherwise resolves to its own empty `workspace/` and reports clean regardless of the code. The rewritten guard is controlled in both directions, so it cannot pass vacuously: HEAD 14/14 pass re-add the flat entry 3 FAIL (config, generated rule, tracking) remove hosts/*/ 5 FAIL (incl. "git tracks the PER-HOST anchor") Suites, all rc=0: tests/vault-carrier-default-current-track.test.sh 14/14 tests/sync-workspace-anchor-migration.test.sh 8/8 tests/sync-workspace-foreign-host-guard.test.sh 21/21 tests/health-check-carrier-set-enforced.test.py rc=0 tests/sutando-config.test.py rc=0 Follow-up, deliberately NOT bundled: health-check's `UNSAFE_TO_READD` becomes dead once the flat path stops being shipped — its own comment says to delete it then. It has five call sites and removing it changes operator-facing message splitting, so it is a separate change, and it should land after #2605 to avoid churn in the same function. Stand: Echo Act IV Mini Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(vault): --push-only must migrate the anchor too, not just the pull path qingyun-wu's P1 on #2607, and it is correct — I verified the call sites rather than take it: `_migrate_flat_anchor` had exactly ONE caller (`_pull_only_impl`), while `_push_only_impl` runs `_enforce_carrier_set_pre` with no migration. The hazard is not the merge, it is the carrier enforcement. `--push-only` regenerates the exclude, untracks the now-uncarried flat `state/current-track.md`, and COMMITS that deletion. On a host whose anchor exists only at the flat path that removes the sole carried copy and writes no replacement — so this PR's "next sync self-heals" claim was false for a supported explicit sync mode. My own worst-case analysis named MacBook-Air as the one host in that shape and then argued a guarantee that did not cover it. Fix is one call before the enforcement, plus a comment saying WHY it belongs in both entry points — the helper's existing rationale argues pull-side placement ("before the fetch/merge") and does not imply the push path, which is how a reader concludes one call site is enough. The helper is idempotent, so the extra call in the bidirectional path costs one `[ -e ]`. Regression drives the REAL script end to end rather than calling the helper, because a direct-call test is exactly what missed this: the existing suite's own header says it "calls the helper directly against one fixture", so the pushing side always had the fix by construction. Control — the new assertions fail with ONLY the push-side call reverted: push-side call removed 12 total, 3 FAIL (the 3 new assertions) at HEAD 12 total, 0 FAIL The refute ("flat path is no longer carried") passes in BOTH states, which is what proves the test measures the RESCUE and not merely the retirement. The 8 pre-existing assertions pass in both states too. Suites, all rc=0: sync-workspace-anchor-migration 12/12, vault-carrier-default-current-track 14/14, sync-workspace-foreign-host-guard 21/21, health-check-carrier-set-enforced. Stand: Echo Act IV Mini Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * test(anchor-migration): make the push-only fixture immune to the caller's env Two defects in the fixture setup, both found by @Sutando-Pro running this suite on a host whose session exports SUTANDO_HOST_LABEL. 1. THE FIXTURE INHERITED THE CALLER'S HOST LABEL. The block set the LEGACY alias `SUTANDO_HOST_OVERRIDE=pushonly-host`. The alias is honored (util_paths.py:113) — it is not dead — but it is LOWER precedence than SUTANDO_HOST_LABEL, which the harness inherits from the invoking shell. A caller that exports it (a real core session does) silently outranks the alias, so the fixture builds under the developer's own host label and the three assertions then read as the FIX failing. Reproduced exactly on this host: before, ambient SUTANDO_HOST_LABEL set -> 12 — pass: 9, fail: 3 after, ambient SUTANDO_HOST_LABEL set -> 12 — pass: 12, fail: 0 after, clean env -> 12 — pass: 12, fail: 0 9/3 is precisely what the reviewer saw. Setting the top-precedence name makes the fixture independent of whatever the caller exports. 2. THE SETUP SWALLOWED ITS OWN FAILURES. Both commands ran under `>/dev/null 2>&1 || true`, so a fixture that was never built was indistinguishable from a change that does not work — the assertions dutifully reported "the anchor was not carried". That is the failure mode that cost the reviewer an hour, and it is the more important half: fixing (1) without (2) leaves the next environmental difference just as invisible. `_po_run` now captures output and, on a non-zero exit, prints the real command output and increments `fail` with a message saying the assertions below measure a fixture that was never built. Verified by breaking the setup deliberately (bad vault URL): FAIL: push-only fixture setup (--init) exited 1 — ... sync-workspace: initialized but push failed; check ... Total: 14 — pass: 12, fail: 2 exit 1 No production change; the fix under test is untouched. Hosted CI was already green on this branch — this makes the suite green for everyone else too. Stand: Echo Act IV Mini --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Co-authored-by: Rui Wang <52230987+john-the-dev@users.noreply.github.com>
The bug
carrier-setcondemns a carrier entry when any file beneath it is git-ignored. Butvault.sync.excludeexists precisely to ignore files inside a carried parent (sync-workspace.sh:_emit_exclude_lines), so on a real workspace the carve-outs fire constantly:Both report
fail— while the vault is demonstrably backing them up.Before (at the parent,
dab9c998)Every claim in that line is false, checked against the vault rather than argued:
And the exclude file is not stale, so the remedy it names is a no-op:
--force-gitignoreregenerates a byte-identical file, so obeying the instruction can never clear the failure. It is a permanent false FAIL that also pushes the operator toward a destructive-looking command for no reason.Why it survived three prior rounds (#2566, #2570, #2576)
The one entry with no carve-out beneath it —
.claude-sutando/projects/*/memory/, 1322 files, 0 ignored — passes. So the probe appeared to discriminate. A probe with exactly one passing case is not a validated probe.After (this branch)
The fix
Ask
check-ignore -vfor the rule that decided each path, and condemn only when that rule.git/info/exclude(the carrier mechanism), andTwo things this turned up that the reported symptom did not contain:
.gitignorecommitted inside a carried tree also condemned the entry. A Remotion app undernotes/ignores its ownout/(12 files). That is git working normally; condemning on it would make every vendored project a permanent FAIL. Hence the source-file check, not just the pattern check.-vsilently changes the exit-code contract the old code read. A path matching a negation — i.e. carried — is still reported and still exits 0:So the verdict now comes from the parsed patterns; only a hard failure (neither 0 nor 1) is still read off the exit code. Verified before depending on it rather than assumed.
Carve-out patterns are mirrored from
vault.sync.exclude+ the hard-deny block rather than parsed out of the live exclude file — that file is the artifact under test, and reading the rules from it would make a stale or hand-edited rule self-justifying, which is the failure #2566 was opened for.Tests
6 new cases in
tests/health-check-carrier-set-enforced.test.py. Run at the parent commit, carrying this branch's test file:At HEAD:
Ran 45 tests ... OK.The last two pass in both directions on purpose — they are the guard that this fix is a narrower question, not a softer one. An operator-authored rule in the exclude file (which
generate_exclude's refusal guard leaves standing because_enforce_carrier_set_preswallows the refusal) still fails, and a genuinely stale carrier rule still fails even when carve-outs are present.One incidental fix: two
subprocess.runstubs declaredstdout = b""while the probe runs git withtext=True. They passed only because nothing read stdout until now.@sonichi