Skip to content

Commit f78df89

Browse files
JonZeollaclaude
andcommitted
fix: replace double quotes with single quotes in NTFS paths
Double quotes are fundamentally illegal in NTFS paths — even the \\?\ extended-length prefix cannot work around this. Replace " with ' during zip extraction, which is safe because Jinja2 treats both quote types identically: replace(" ", "") and replace(' ', '') produce the same output. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent c75e86f commit f78df89

1 file changed

Lines changed: 10 additions & 13 deletions

File tree

.github/workflows/ci.yml

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -129,9 +129,10 @@ jobs:
129129
git config --global user.email "ci@zenable.io"
130130
131131
# The template directory name contains double-quote characters which
132-
# are fundamentally illegal on NTFS — git clone and normal extraction
133-
# both fail. Extract using Python with the \\?\ extended-length path
134-
# prefix, which bypasses NTFS name validation on Windows.
132+
# are fundamentally illegal on NTFS. Download the zip and extract with
133+
# Python, replacing " with ' in paths during extraction. This is safe
134+
# because Jinja2 treats single and double quotes identically, so
135+
# replace(" ", "") and replace(' ', '') produce the same output.
135136
$zipUrl = "https://github.com/${{ github.repository }}/archive/$env:TEMPLATE_REF.zip"
136137
$zipPath = Join-Path $env:RUNNER_TEMP "template.zip"
137138
$extractDir = Join-Path $env:RUNNER_TEMP "template-src"
@@ -140,23 +141,19 @@ jobs:
140141
python -c @"
141142
import zipfile, os, sys
142143
zf = zipfile.ZipFile(sys.argv[1])
143-
raw_dest = sys.argv[2]
144-
# Use extended-length path prefix to bypass NTFS char restrictions.
145-
# The prefix requires backslashes and no trailing slashes.
146-
prefix = '\\\\?\\' + os.path.abspath(raw_dest) if os.name == 'nt' else raw_dest
144+
dest = sys.argv[2]
147145
for info in zf.infolist():
148-
# Normalize zip forward slashes to OS path separators
149-
clean = info.filename.replace('/', os.sep).rstrip(os.sep)
150-
target = os.path.join(prefix, clean)
146+
name = info.filename
147+
if os.name == 'nt':
148+
name = name.replace(chr(34), chr(39))
149+
target = os.path.join(dest, name.replace('/', os.sep).rstrip(os.sep))
151150
if info.is_dir():
152151
os.makedirs(target, exist_ok=True)
153152
else:
154153
os.makedirs(os.path.dirname(target), exist_ok=True)
155154
with zf.open(info) as src, open(target, 'wb') as dst:
156155
dst.write(src.read())
157-
# Print the top-level directory (without the \\?\ prefix for downstream use)
158-
top = os.listdir(prefix)[0]
159-
print(os.path.join(raw_dest, top))
156+
print(os.path.join(dest, os.listdir(dest)[0]))
160157
"@ $zipPath $extractDir | Set-Variable templateDir
161158
162159
uvx --with gitpython cookiecutter $templateDir.Trim() --no-input --output-dir "$env:RUNNER_TEMP"

0 commit comments

Comments
 (0)