Skip to content

emrg: the rotation reading tells two spellings of one file apart, so the bookmark counts as memory #2559

Closed

Description

@argszero

What is observed

scripts/check-memory-rotation.py::_same_file decides whether two paths are one file by
comparing resolved strings:

def _same_file(a: Path, b: Path) -> bool:
    """Whether two paths name one file, through symlinks and a relative spelling alike."""
    try:
        return os.path.realpath(a) == os.path.realpath(b)
    except OSError:
        return False

os.path.realpath resolves symlinks and .., and then returns the spelling it was handed
for the components that exist. On a volume that folds case, two spellings of one file
therefore compare unequal — and the volume itself says they are one file.

The helper's one caller is the exclude argument of memory_files: the bookmark is the
clock, not memory content, so it is removed from the scan (#2422). When the exclusion is
spelled differently from the walk, it is not removed.

Where it was observed

macOS 26.6.2 (APFS, case-folding), master 5c6b832d, on this checkout's own bookmark
.emrg/memory/project-memory-rotation-bookmark.md. Measured through the script's own
functions, loaded by path:

bookmark exists      : True
variant exists       : True
os.path.samefile     : True          # the volume's answer
script _same_file    : False         # the script's answer
realpath(variant)    : .../memory/PROJECT-MEMORY-ROTATION-BOOKMARK.MD
realpath(bm)         : .../memory/project-memory-rotation-bookmark.md
files with true bookmark excluded : 127
files with CASED bookmark excluded: 128
bookmark counted when excluded by its cased spelling: True

The one-file difference is the bookmark itself. That is the exact state #2422 removed: the
bookmark counted as the memory of the agent that holds it, which makes that agent's row read
changed on the strength of the file that asks the question.

What was expected instead

The same answer os.path.samefile gives: two spellings that name one file are one file,
decided by the filesystem's identity (st_dev + st_ino) rather than by a comparison of
strings. emrg/tools/file_policy.py::same_file states this rule for the rest of the tree and
documents why (#2552); emrg/tools/shell_env.py::_same_directory delegates to it (#2558).

What the fix must satisfy

  1. Two spellings of one file are one file on a volume that folds case, whether or not the
    platform is Windows. Decide by identity, not by a transform of the paths.
  2. A path that is not there still gets an answer: it is not the file it is compared with, so
    it is not excluded. Excluding a path that does not exist would hide a real memory file.
  3. The exclude behaviour emrg: the rotation bookmark counts itself as memory, so the project holding it always reads as changed #2422 established does not regress: the bookmark is still removed
    when it is spelled the way the walk spells it.
  4. Both directions are measured, and the case-variant arm skips with its reason stated where
    the volume keeps two spellings apart, rather than passing without asking.

Note on where the rule should live

This script is deliberately standalone — it imports nothing from the package, so its test
harness puts no tree on sys.path (see the header of tests/test_check_memory_rotation.py).
That constraint is in tension with "one home for the rule": either the script asks
os.path.samefile directly and the tension is recorded in its docstring, or it imports the
home and the standalone property is given up deliberately. Either is defensible; leaving two
different answers to "are these one file" in one repository is not.

Activity

  1. argszero commented on Oct 23, 2026

    @argszero
    OwnerAuthor

    Handled by #2562

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions