Skip to content

fix(graphify): canonicalize temp base so the isolated-cwd assertion is satisfiable on macOS - #52

Merged
KimYx0207 merged 1 commit into
KimYx0207:mainfrom
qitiandashenggogogo:fix/graphify-normalizer-macos-realpath
Aug 8, 2026
Merged

fix(graphify): canonicalize temp base so the isolated-cwd assertion is satisfiable on macOS#52
KimYx0207 merged 1 commit into
KimYx0207:mainfrom
qitiandashenggogogo:fix/graphify-normalizer-macos-realpath

Conversation

@qitiandashenggogogo

Copy link
Copy Markdown
Contributor

Problem

On macOS, npm run meta:graphify:check always fails:

Python 3.12.9
graphify 0.9.31
Graphify Python node-ID normalizer is unavailable: Graphify normalizer cwd is not a plain isolated directory

Reproduced on unmodified origin/main (2ca78b93) with a zero-diff worktree.

Root cause

scripts/graphify-unicode-normalize.mjs created the isolated cwd under a raw
os.tmpdir() and then asserted that the path is symlink-free by comparing
realpathSync.native(isolatedCwd) with path.resolve(isolatedCwd).

On macOS os.tmpdir() is /var/folders/..., and /var is a system-owned
symlink to /private/var
, so the two sides can never be equal:

value result
path.resolve(isolatedCwd) /var/folders/.../meta-kim-graphify-normalizer-XXXX
realpathSync.native(isolatedCwd) /private/var/folders/.../meta-kim-graphify-normalizer-XXXX
lstat().isDirectory() / isSymbolicLink() true / false (the directory itself is fine)

The assertion is therefore unsatisfiable on every macOS machine, regardless of
repository state. A repo-wide grep shows this is the only site with that
comparison shape.

Fix

Canonicalize the temp base before mkdtempSync, so the created path is
already canonical. The three assertion lines are left byte-for-byte
unchanged.

+  // Canonicalize the temp base before creating the directory: on macOS
+  // os.tmpdir() is reached through the system /var -> /private/var symlink, so
+  // an uncanonicalized base would make the plain-directory assertion below
+  // impossible to satisfy. Resolving it first keeps that assertion unchanged.
   const isolatedCwd = mkdtempSync(
-    path.join(tmpdir(), "meta-kim-graphify-normalizer-"),
+    path.join(realpathSync.native(tmpdir()), "meta-kim-graphify-normalizer-"),
   );

Why not "realpath both sides"

The obvious alternative — comparing realpathSync.native(isolatedCwd) against
realpathSync.native(path.resolve(isolatedCwd)), or against
path.join(realpathSync.native(tmpdir()), path.basename(isolatedCwd)) — makes
both sides identical by construction. That silently deletes a security
assertion
guarding the cwd handed to spawnSync for the Python batch. This
patch deliberately avoids weakening it.

Negative proof the assertion still bites

With the fix applied, replacing the freshly created directory with a symlink
pointing elsewhere still trips the guard:

leaf is now a symlink = true
realpath.native  = /private/var/folders/.../attacker-qWtsZU
path.resolve     = /private/var/folders/.../meta-kim-graphify-normalizer-lWlfZG
assertion throws = true

Verification

Direct before/after on the same call:

# HEAD~1 (pre-fix)
THREW: Graphify normalizer cwd is not a plain isolated directory
# with fix
OK, descriptor = graphify-0.9.31-module-ac9138fccaa3-python-unicode-15.0.0-live-v2

Existing regression coverage already exercises this path — no new test needed:

node --test tests/setup/graphify-node-identity.test.mjs
# tests 17 / pass 17 / fail 0

That suite calls createGraphifyRuntimeNormalizer with non-ASCII values, so it
fails on macOS before this patch and passes after.

Scope and limits

  • This does not make meta:graphify:check exit 0. Once the normalizer gate
    passes, a second pre-existing failure surfaces at
    scripts/graphify-cli.mjs:691:
    GRAPH_REPORT.md exposes a private local path.
    hasPrivateLocalPath (scripts/graphify-private-path.mjs:3) treats a bare
    ~/ as a leak, while sanitizeKnownMetaKimHomeAliases only sanitizes
    ~/.meta-kim. The report faithfully indexes a tracked upstream comment,
    canonical/runtime-assets/claude/commands/save-progress/SKILL.md:19
    (# Detect the Python hook path — it lives in ~/.claude/hooks/), so any user
    hits it after a rebuild. That fix involves a design trade-off (widen the
    sanitizer, distinguish documentation references from real local leaks, or
    reword the source comment) and is intentionally left out of this PR. Happy to
    follow up if you have a preferred direction.
  • Verified on macOS 26.5.2, Node v24.12.0. Windows is untested;
    realpathSync.native behaviour there (8.3 short names, case) is unchanged by
    this patch in reasoning only, not by measurement.

macOS 的 os.tmpdir() 经系统软链 /var -> /private/var 到达,导致
realpathSync.native(cwd) 与 path.resolve(cwd) 永不相等,隔离目录断言
在每台 Mac 上恒抛错,meta:graphify:check 恒 EXIT=1。

改为在 mkdtemp 之前先规范化 tmp base,断言那三行原样保留、未削弱:
把建好的目录换成指向别处的软链时仍然抛错(已实测反证)。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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.

2 participants