Skip to content

fix(pull): key a checkout by its canonical path, so a differently spelled HOME finds its record (#1743) - #1792

Closed
Walternater wants to merge 1 commit into
Tencent:mainfrom
Walternater:fix/checkout-key-canonical
Closed

Walternater wants to merge 1 commit into
Tencent:mainfrom
Walternater:fix/checkout-key-canonical

Conversation

@Walternater

Copy link
Copy Markdown
Collaborator

Problem

checkoutKey (src/pull.ts) hashes the string it is given. The same directory spelled with a trailing separator, or with a . segment, hashes differently, so it gets a different entry in state.lastPullByWorkspace:

checkoutKey('/home/alice')  -> 91515ad30210
checkoutKey('/home/alice/') -> baafd25220df

So a command launched with the other spelling reads nothing the last pull wrote. The user-scope record holds rev, targets, delivered — the path and hash of every copy teamai wrote, which is what proves a delivered file is teamai's — places, sessionToolHomes and selection. A pull that misses it runs a full sync instead of the unchanged fast path.

#1743 is the report of what that costs in a flow where the re-delivery does not heal it: a copy teamai delivered is then read as the member's own, because the record that proves otherwise is not found. The triage split the issue into this record-key part and the ownership-proof part; the proof itself was fixed in #1764. This PR is the key.

Change

One line: hashing the resolved path instead of the given one.

const id = managedMcpWorkspaceId(path.resolve(projectRoot));

path.resolve is pure — no realpath, so a home that is a symlink keeps its key and no one's record moves unexpectedly.

Real CLI, after npm run build

Sandbox: an isolated HOME, a local team repo holding one skill ledger, user scope, enabledAgents: [claude].

pull A: HOME=/tmp/t1743-fixed/home     -> Synced 1 skills (1 new)
pull B: HOME=/tmp/t1743-fixed/home/    -> ?
pull B record key after both pulls
main Synced 1 skills (all updated) baafd25220df — pull A's record is gone
this branch Already synced at 5acfd7a, skipping 91515ad30210 — the key pull A wrote

And a single pull each way, on the same directory:

HOME spelling main this branch
/tmp/t1743-fixed/home 91515ad30210 91515ad30210
/tmp/t1743-fixed/home/ baafd25220df 91515ad30210

What a key holds, from the same sandbox:

"delivered": {"/tmp/t1743-fixed/home/.claude/skills/ledger/SKILL.md": "bc0b1468…c44c25"}

That map is the proof a delivered copy is teamai's. A command that computes the other key finds no record at all.

Tests

New src/__tests__/checkout-key.test.ts states the property: one directory, one key, however its path is spelled (trailing separator, . segment, .. segment). It fails on main:

Expected: "ad525b64ac79"
Received: "b5a5eac30de2"

npx vitest run — the 10 files that read checkoutKey and pull state: 587 passed. npm run typecheck and npm run lint pass.

Limitations

  • A user whose record is already under a non-canonical spelling does one full sync after upgrading, then writes the canonical record. The old entry stays in state.json; it names no live checkout with a root, and user-scope records never had one, so nothing drops it and nothing else reads it.
  • Symlinks and Windows drive-letter case are still not unified: the first needs I/O, and the second would move every existing Windows key. The reported cause is a separator, and there is no evidence of the other two on this issue.

Part of #1743.

🤖 Generated with Claude Code

…lled HOME finds its record (Tencent#1743)

`checkoutKey` hashed the path it was given, so one directory spelled with a
trailing separator or a `.` segment hashed to a different entry in
`state.lastPullByWorkspace`. The pull under the second spelling then misses
the record the first one wrote, runs a full sync instead of the unchanged
fast path, and the entry holding `delivered` — the proof a copy teamai wrote
is teamai's — is superseded.

Resolve the path before hashing it. `path.resolve` is pure, so a home that is
a symlink keeps its key.

Co-Authored-By: Claude Code <noreply@anthropic.com>
@Walternater

Copy link
Copy Markdown
Collaborator Author

Closing this: #1780 (05fe72a2, now on main) already fixes the same record-key defect, and does it better. Its canonicalUserHome() is the path.resolve I used, plus it also rewrites a record's delivered paths across HOME aliases (symlink, Windows casing) and migrates a legacy record rather than leaving it behind — none of which this branch had.

Two things this branch did that #1780 rightly left out, so it is not a smaller alternative worth merging:

I had not fetched origin/main before opening this. Sorry for the duplicate.

@Walternater Walternater closed this Nov 8, 2026
@Walternater
Walternater deleted the fix/checkout-key-canonical branch November 8, 2026 16:24
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.

1 participant