Skip to content

fix(health-check): a symlink alias must not make a carrier entry unmeasurable - #2665

Merged
qingyun-wu merged 2 commits into
mainfrom
fix/carrier-probe-symlink-alias
Aug 5, 2026
Merged

qingyun-wu merged 2 commits into
mainfrom
fix/carrier-probe-symlink-alias

Conversation

@qingyun-wu

Copy link
Copy Markdown
Collaborator

What

git check-ignore rejects any pathspec that traverses a symlink (exit 128, fatal: pathspec '…' is beyond a symbolic link), and the carrier probe folds any non-0/1 exit into UNMEASURED for the whole entry. A compat alias symlink under .claude-sutando/projects/ (a space-slug project dir pointing at its dash-slug twin, created for cross-slug session resolution) is matched by the .claude-sutando/projects/*/memory/ glob, so the memory entry read could NOT measure … UNKNOWN, not fine on every health pass — while the content was backed up the entire time at its real path, which the same entry probes via the twin's own materialization.

Fix

Git never stores content past a symlink — at most the link entry itself — so a materialization that reaches through one is outside what the vault could ever carry. _carrier_representatives now filters matches whose path is a symlinked directory or crosses a symlink below the workspace (_reaches_through_symlink), for both the stale and dropped branches (the single-witness form delegates to it).

Why not probe the crossing link itself: that trades the false UNMEASURED for a false DROPPED. Measured on the live host — dir-only un-ignore patterns (!projects/*/) cannot match a symlink, so the link reads ignored:

$ printf '.claude-sutando/projects/<alias-slug>' | git check-ignore --no-index -z --stdin -v
.git/info/exclude 10 * .claude-sutando/projects/<alias-slug>     # matched by '*', no un-ignore reaches it

A symlink to a FILE with a real parent chain is deliberately not filtered: git accepts that pathspec and file rules match it, so existing behavior stands.

Evidence

Repro on the parent commit (live workspace, alias symlink present):

$ git check-ignore --no-index -z --stdin < <(find .claude-sutando/projects/*/memory -type f -print0)
fatal: pathspec '.claude-sutando/projects/<alias-slug>/memory/reference_tauri_acl_grant_trap.md' is beyond a symbolic link
exit=128

PARENT(main): status=fail  detail=could NOT measure 1 carrier path(s) (.claude-sutando/projects/*/memory/) — git was unavailable or check-ignore failed, s
HEAD(fix):    status=ok    detail=all 3 configured carrier path(s) un-ignored in the vault

New tests on the parent commit (first two FAIL with the exact live symptom, control passes):

test_a_symlinked_project_ALIAS_does_not_make_the_entry_unmeasured ... FAIL   ('fail' != 'ok' … could NOT measure 1 carrier path(s) (proj/*/memory/))
test_an_entry_matching_a_symlinked_DIRECTORY_itself_stays_measured ... FAIL  ('fail' != 'ok' … could NOT measure 1 carrier path(s) (hosts/*/))
test_a_symlink_does_not_MASK_a_genuinely_dropped_sibling ... ok

At HEAD:

Ran 49 tests in 0.996s
OK

Four new tests: the live alias shape, a glob matching a symlinked directory itself, a pin that the tree walk does not follow directory symlinks (passes today; guards a future walk change from re-opening the class), and a discriminator control proving a genuinely dropped sibling still fails alongside a symlink.

Scope

Probe-only; no change to sync, exclude generation, or any live path. Single concern.

🤖 Generated with Claude Code

https://claude.ai/code/session_01KiaMLCxgKNAbmhty29WGN6

…asurable

git check-ignore rejects any pathspec that traverses a symlink (exit 128,
'beyond a symbolic link'), and the carrier probe folds 128 into UNMEASURED
for the whole entry. A compat alias symlink under .claude-sutando/projects/
(space-slug project dir pointing at its dash-slug twin) therefore made the
memory entry read 'could NOT measure' on every health pass — while the
content was backed up the entire time at its real path.

Git never stores content past a symlink, so a materialization that reaches
through one is outside what the vault could ever carry: filter it out of
_carrier_representatives. Probing the crossing link itself instead would
trade the false UNMEASURED for a false DROPPED (dir-only un-ignore patterns
like !projects/*/ cannot match a symlink; measured on the live host).

Four tests: the live alias shape, a glob matching a symlinked directory
itself, a pin that the tree walk does not follow directory symlinks, and a
discriminator control proving a genuinely dropped sibling still fails.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KiaMLCxgKNAbmhty29WGN6

@bassilkhilo-ag2 bassilkhilo-ag2 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Reviewed exact head a573b14 in a scratch clone.

Mutation test confirms the fix is load-bearing and correctly scoped. Reverted _carrier_representatives to drop the _reaches_through_symlink filter — exactly 2 of 49 tests fail (test_a_symlinked_project_ALIAS_does_not_make_the_entry_unmeasured, test_an_entry_matching_a_symlinked_DIRECTORY_itself_stays_measured), nothing else. Restored, re-ran: 49/49.

Discriminator control verified separately (test_a_symlink_does_not_MASK_a_genuinely_dropped_sibling): a symlink alias sitting next to a genuinely un-carried sibling still fails correctly — the fix filters the false-UNMEASURED case without softening real drops. And the file-vs-directory distinction in _reaches_through_symlink (a symlink to a FILE with a real parent chain is deliberately left unfiltered, since git can store that pathspec) is exercised by the existing symlinked-subdir test staying green.

Root-cause reasoning holds up: git genuinely can't store content past a symlink (fatal: pathspec '...' is beyond a symbolic link, exit 128), so folding that into UNMEASURED was masking a real backup as unknown rather than confirmed. The alternative considered and rejected (probing the crossing link itself) is correctly identified as trading a false UNMEASURED for a false DROPPED — dir-only un-ignore patterns can't match a symlink, so that path would read as ignored/dropped even when the real content is fine.

CI green except tsc + tests and diff coverage still running at review time. Mergeable. No blocker.

@john-the-dev john-the-dev left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Reviewed exact head a573b14777b36c9627abdfb94f8f7c5d66156058.

Evidence:

  • Independently reproduced the live alias shape against origin/main: check_carrier_set_enforced() returned fail / could NOT measure ...; the same fixture at HEAD returns ok.
  • Activated same-entry discriminator: with the real wildcard sibling genuinely still ignored and the alias present, HEAD returns fail naming proj/*/memory/ rather than letting the filtered alias mask it.
  • python3 tests/health-check-carrier-set-enforced.test.py: 49/49 pass.
  • Every tests/health-check*.test.py file: 51/51 pass.
  • py_compile, git diff --check, repository REVIEW hardcoded-path check, and independent added-line path scan pass.

Worst-case disruption: filtering a symlink materialization could hide a real dropped path. The implementation filters only paths beyond a directory symlink, retains real sibling representatives, and the same-entry dropped discriminator stays fail-closed. No mutation/sync path changes.

APPROVE for the code at this exact head. Not yet merge-ready: required clean-install and Python diff-coverage jobs are still in progress, and repository policy still requires another formal maintainer approval.

@github-actions

github-actions Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Coverage Gate

✅ Diff coverage PASSES the 95% bar. Whole-tree (informational): 77%.

Diff Coverage

Diff: origin/main...HEAD, staged and unstaged changes

  • src/health-check.py (100%)

Summary

  • Total: 14 lines
  • Missing: 0 lines
  • Coverage: 100%

@qingyun-wu

Copy link
Copy Markdown
Collaborator Author

Reviewed exact head a573b14777b36c9627abdfb94f8f7c5d66156058.

No blocking findings. The symlink-alias filter is narrowly scoped to directory-symlink materializations that Git cannot measure past, while preserving file-symlink behavior and the genuinely dropped sibling failure path.

Focused verification passed: python3 -B tests/health-check-carrier-set-enforced.test.py (49 tests), Python compilation for the touched files, git diff --check origin/main...HEAD, and the REVIEW.md path gate. Hosted diff coverage and lint jobs were green when checked; tsc + tests (clean install) was still in progress, so merge should wait for that required job to finish green.

APPROVE - code-ready pending the remaining hosted check.

Reviewed by Qingyun's Personal Codex.

@github-actions

github-actions Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

@cla-assistant check

@qingyun-wu
qingyun-wu enabled auto-merge (squash) August 5, 2026 08:09
@qingyun-wu
qingyun-wu merged commit 052a14a into main Aug 5, 2026
18 checks passed
@qingyun-wu
qingyun-wu deleted the fix/carrier-probe-symlink-alias branch August 5, 2026 08:09
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.

3 participants