[BUG] code-modernization plugin: make_shards.py contained() uses os.path.realpath() — breaks topology sharding on Windows junction-based shadows (0 shards produced)

Status Open
Reported on v2.1.283
Maintainer reply None cached
Activity 0 comments · opened Sep 28, 2026

Preflight Checklist

  • [x] I have searched existing issues and this hasn't been reported yet
  • [x] This is a single bug report (please file separate reports for different bugs)
  • [x] I am using the latest version of Claude Code

What's Wrong?

contained() in scripts/make_shards.py (lines 84–85) uses os.path.realpath(), which follows Windows directory junctions. LAMP's ensureLegacyShadow creates legacy/<system>/ as a real directory with Windows junctions for each top-level subdirectory pointing to the actual source. When os.path.realpath() resolves a module path through one of those junctions, the resolved path lands outside legacy/<system>/ — every topology module is classified as "escaped" and dropped. from_topology() returns an empty list with no exception, so the from_tree fallback never triggers.

Result: 0 shards, extract-rules.modules.json never written, /modernize-extract-rules produces no output.

Environment:

  • Plugin: code-modernization (anthropics/claude-plugins-official), version 1.0.0
  • Claude Code: 2.1.283
  • OS: Windows 11 Enterprise
  • File: scripts/make_shards.py, lines 84–85

Root cause:

root = os.path.realpath(source)                      # stays at legacy/myapp/ (real dir)
real = os.path.realpath(os.path.join(source, rel))  # follows junction → resolves outside legacy/myapp/
# real.startswith(root + sep) → False → module classified escaped

Fix: replace both os.path.realpath calls with os.path.abspath:

def contained(source, rel):
    root = os.path.abspath(source)
    real = os.path.abspath(os.path.join(source, rel))
    return real == root or real.startswith(root + os.sep)

abspath resolves .. traversal (the original safety intent) without following junctions.

What Should Happen?

make_shards.py should produce the correct number of shards (one per topology module) when legacy/<system>/ is a Windows junction-based shadow. from_topology() should classify all topology modules as contained and write extract-rules.modules.json. The contained() check must not follow Windows directory junctions.

Error Messages/Logs

Steps to Reproduce

  1. Create source with subdirectories on Windows:

``
mkdir C:\repro\source\myapp\src
mkdir C:\repro\source\myapp\domain
``

  1. Create the junction-based shadow (what LAMP's ensureLegacyShadow does on Windows):

``
mkdir C:\repro\project\legacy\myapp
cmd /c mklink /J C:\repro\project\legacy\myapp\src C:\repro\source\myapp\src
cmd /c mklink /J C:\repro\project\legacy\myapp\domain C:\repro\source\myapp\domain
``

  1. Create C:\repro\project\analysis\myapp\topology.json:

``json
{"root":{"kind":"domain","name":"root","children":[
{"kind":"module","name":"order-service","file":"src/","loc":500,"children":[]},
{"kind":"module","name":"claim-domain","file":"domain/","loc":300,"children":[]}
]}}
``

  1. Run:

``
python scripts/make_shards.py myapp --workspace C:\repro\project\
``

Expected: 2 shards from 2 map modules -> analysis/myapp/extract-rules.modules.json
Actual: 0 shards: the pattern matched nothing, or no source files were found. Nothing was written.

Claude Model

None

Is this a regression?

Yes, this worked in a previous version

Last Working Version

_No response_

Claude Code Version

2.1.283

Platform

Anthropic API

Operating System

Windows

Terminal/Shell

PowerShell

Additional Information

_No response_

View original on GitHub ↗