Skip to content

Harden against Windows-breaking symlinks (follow-up to #203) - #206

Closed
T-Nid (tyaginidhi) wants to merge 3 commits into
mainfrom
users/nityagi/windows-symlink-followup
Closed

Harden against Windows-breaking symlinks (follow-up to #203)#206
T-Nid (tyaginidhi) wants to merge 3 commits into
mainfrom
users/nityagi/windows-symlink-followup

Conversation

@tyaginidhi

Copy link
Copy Markdown
Contributor

Follow-up to #203 (which fixed #201). #203 replaced the legacy .claude-plugin manifest symlinks with real JSON files, unblocking marketplace install on Windows. This PR closes two remaining gaps surfaced during review of #203.

1. Take in the open review suggestion on #203 (validator hardening)

Copilot noted on #203 that assertJsonMirror only compares parsed JSON, so a legacy manifest re-committed as a symlink would still pass on Linux/CI (which resolves symlinks) — letting the exact #201 regression back in undetected. CI runs on ubuntu-latest, so a content-only check never catches the Windows-only breakage.

validate-legacy-compatibility.js now asserts each legacy manifest is a committed regular file (not a symlink) before comparing content. This lstat-based assertion is what actually guards the cross-platform fix.

2. Convert the remaining CLAUDE.md symlinks (the follow-up hazard)

The four CLAUDE.md files (root + canvas-apps / model-apps / power-pages) were still symlinks to their sibling AGENTS.md, carrying the same #201 hazard: on a default Windows clone (core.symlinks=false, Developer Mode off) git materializes them as 9-byte text files containing AGENTS.md. They don't block marketplace registration (so they're out of scope for #203), but they degrade the installed plugin.

  • Converted the 4 symlinks to real file copies of their sibling AGENTS.md.
  • Extended the validator to guard them: regular file and byte-identical to AGENTS.md, checked only where a CLAUDE.md exists (so code-apps / mcp-apps, which have none, don't false-fail).

Deliberately out of scope

The DRY shared-content symlinks — report-issue-workflow.md (×5), telemetry-workflow.md, and scripts/lib/telemetry/lib — are left as-is. They rely on the marketplace installer dereferencing them (documented in AGENTS.md/CLAUDE.md, PR #185), and converting them to real files would duplicate shared content against that documented architecture. Flagging for a separate decision if full raw-clone portability is wanted.

Verification

  • node scripts/validate-legacy-compatibility.js → passes on the clean tree.
  • Negative tests (each restored after): re-introduced manifest symlink → fails; re-introduced CLAUDE.md symlink → fails; CLAUDE.md content drift → fails.
  • validate-plugin-names.js and validate-skill-descriptions.js still pass.
  • Confirmed no symlinks remain under any .claude-plugin/ path or among the CLAUDE.md files.

🤖 Generated with Claude Code

…+ convert CLAUDE.md mirrors

Follow-up to #203. That PR fixed the issue #201 install failure by replacing the
legacy .claude-plugin manifest symlinks with real JSON files, but two gaps remained:

1. The validator only compared parsed JSON, so a manifest re-committed as a symlink
   would still pass on Linux/CI (which resolves symlinks) and let the #201 regression
   back in. Per the open review suggestion on #203, assert each legacy manifest is a
   committed regular file (not a symlink) before comparing — this is the assertion
   that actually guards the cross-platform fix.

2. The per-directory CLAUDE.md files were still symlinks to AGENTS.md, carrying the
   same Windows hazard (git materializes them as tiny text files on core.symlinks=off
   clones). Convert the 4 CLAUDE.md symlinks (root + canvas-apps/model-apps/power-pages)
   to real file copies and extend the validator to guard them (regular file + identical
   content to the sibling AGENTS.md, only where CLAUDE.md exists).

The DRY shared-content symlinks (report-issue/telemetry workflows, telemetry/lib) are
intentionally left as-is: they rely on the marketplace installer dereferencing them and
converting them would duplicate shared content against the documented architecture.

Verified: validator passes clean; fails on a re-introduced manifest symlink, a
re-introduced CLAUDE.md symlink, and CLAUDE.md content drift. validate-plugin-names
and validate-skill-descriptions still pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@tyaginidhi
T-Nid (tyaginidhi) requested a review from a team as a code owner June 22, 2026 10:29
Copilot AI review requested due to automatic review settings June 22, 2026 10:29

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR further hardens the repo against Windows-breaking symlink materialization by (1) strengthening validate-legacy-compatibility.js to reject legacy mirrors committed as symlinks (not just content-different), and (2) converting CLAUDE.md symlinks into real file copies of their sibling AGENTS.md, with validator checks to keep them identical.

Changes:

  • Add an lstat-based “must be a committed regular file” assertion to prevent .claude-plugin mirrors from being reintroduced as symlinks.
  • Add validation for CLAUDE.md mirrors (regular file + identical content to AGENTS.md) only in directories where CLAUDE.md exists.
  • Convert remaining CLAUDE.md symlinks into real files (root + selected plugins) to avoid Windows checkout degradation.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +78 to +80
if (!fs.existsSync(claudePath)) {
return;
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good catch — fixed in 9879a03. Switched the presence test from existsSync (which follows symlinks) to lstatSync, skipping only on ENOENT. Now a dangling CLAUDE.md symlink reaches assertRegularFile and fails instead of being silently skipped. Added a negative test covering exactly that case (dangling symlink → existsSync reports absent, validator now exits 1).

T-Nid (tyaginidhi) and others added 2 commits June 22, 2026 16:46
…not existsSync

Address review feedback on #206: existsSync follows symlinks, so a dangling
CLAUDE.md symlink (link to a missing target) reported as absent and skipped the
very regular-file guard meant to reject symlinks. Switch the presence test to
lstatSync (non-following) and skip only on ENOENT, so any committed symlink —
resolving or dangling — reaches assertRegularFile and fails the check.

Verified: a dangling CLAUDE.md symlink now fails (previously passed); clean tree
still passes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Addresses the two non-blocking gaps raised in review of #206.

Test coverage gap: CI only ran validate-legacy-compatibility.js against the real
(always-clean) repo, exercising the happy path but never the symlink-rejection /
drift-detection logic — a future refactor could drop assertRegularFile and CI would
stay green. Add a LEGACY_COMPAT_ROOT seam so the script can validate a fixture tree,
and scripts/tests/validate-legacy-compatibility.test.js (node:test) which plants each
regression — symlinked manifest, symlinked CLAUDE.md, dangling CLAUDE.md symlink,
content drift — and asserts the validator exits non-zero. Wired into CI as a new
`node --test scripts/tests/` step in validate-repository-metadata. Symlink-creation
cases self-skip where the platform forbids symlinks; the ubuntu CI runner runs them all.

Doc gap: the Legacy Marketplace Compatibility section documented the manifest-mirror
sync rule but not the new CLAUDE.md one. Add a paragraph stating each CLAUDE.md is a
committed real-file mirror of its sibling AGENTS.md (never a symlink) that must be
updated in the same change, enforced by the validator + its tests. Re-synced root
CLAUDE.md to match the edited AGENTS.md.

Verified: 7/7 tests pass; validator still green on the real repo; plugin-names and
skill-descriptions checks unaffected.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings June 22, 2026 11:28

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 8 out of 12 changed files in this pull request and generated no new comments.

@tyaginidhi

Copy link
Copy Markdown
Contributor Author

addressed in a different PR

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.

[power-pages][Bug] Unable to install power platform skills marketplace - JSON parse error with marketplace.json

2 participants