Hello,
I'm sorry for the delay due to my vacation. Here are details from GH CI.
Jiri
Run .\.github\scripts\debug_environment.ps1
.\.github\scripts\debug_environment.ps1
shell: C:\Windows\System32\WindowsPowerShell\v1.0\powershell.EXE -command
". '{0}'"
env:
SCOOP: C:\Users\runneradmin\scoop
=================== OS & SYSTEM INFO ===================
Caption : Microsoft Windows Server 2025 Datacenter
Version : 10.0.26100
BuildNumber : 26100
OSArchitecture : 64-bit
SystemDirectory : C:\Windows\system32
LastBootUpTime : 9/13/2026 5:27:39 PM
PSVersion : 5.1.26100.33296
PSEdition : Desktop
CLRVersion : 4.0.30319.42000
ProcessorArch : AMD64
AnsiCodePage : 1252
LongPathsEnabled : 1
ProbedVolumes : D:\a\scoop-bucket\scoop-bucket,
C:\Users\runneradmin\scoop
=================== FILESYSTEM INFO ===================
--- volume D: (probe root: D:\a\scoop-bucket\scoop-bucket) ---
DriveLetter : D
FileSystemLabel : Temporary Storage
FileSystem : NTFS
FileSystemType : NTFS
DriveType : Fixed
AllocationUnitSize : 4096
Size : 161059172352
SizeRemaining : 157865340928
DeviceID : D:
VolumeName : Temporary Storage
FileSystem : NTFS
MaximumComponentLength : 255
SupportsFileBasedCompression : True
VolumeSerialNumber : 922614C0
GetVolumeInformationW: label=Temporary Storage fs=NTFS serial=922614C0
maxComponentLength=255 flags=0x03E72EFF
flags set: FILE_CASE_PRESERVED_NAMES, FILE_UNICODE_ON_DISK,
FILE_FILE_COMPRESSION, FILE_VOLUME_IS_COMPRESSED, , , , , , , ,
--- volume C: (probe root: C:\Users\runneradmin\scoop) ---
DriveLetter : C
FileSystemLabel : Windows
FileSystem : NTFS
FileSystemType : NTFS
DriveType : Fixed
AllocationUnitSize : 4096
Size : 160467759104
SizeRemaining : 33966383104
DeviceID : C:
VolumeName : Windows
FileSystem : NTFS
MaximumComponentLength : 255
SupportsFileBasedCompression : True
VolumeSerialNumber : D6F2A281
GetVolumeInformationW: label=Windows fs=NTFS serial=D6F2A281
maxComponentLength=255 flags=0x03E72EFF
flags set: FILE_CASE_PRESERVED_NAMES, FILE_UNICODE_ON_DISK,
FILE_FILE_COMPRESSION, FILE_VOLUME_IS_COMPRESSED, , , , , , , ,
=================== C RUNTIME fopen() TRAILING-SEPARATOR TEST
===================
--- volume D:, probe directory
D:\a\scoop-bucket\scoop-bucket\ctx-probe-b967d7f6d6fb4084b0c7a5ecfbafdf78
---
ucrtbase
fopen("D:\a\scoop-bucket\scoop-bucket\ctx-probe-b967d7f6d6fb4084b0c7a5ecfbafdf78\marker_ucrtbase_fwdslash/",
"wb") -> FAILED errno=22 (Invalid argument)
-> nothing created on disk
ucrtbase
fopen("D:\a\scoop-bucket\scoop-bucket\ctx-probe-b967d7f6d6fb4084b0c7a5ecfbafdf78\marker_ucrtbase_backslash\",
"wb") -> FAILED errno=22 (Invalid argument)
-> nothing created on disk
ucrtbase
fopen("D:\a\scoop-bucket\scoop-bucket\ctx-probe-b967d7f6d6fb4084b0c7a5ecfbafdf78\marker_ucrtbase_plain",
"wb") -> OPENED
-> created: marker_ucrtbase_plain [FILE, 0 bytes]
attributes=Archive
msvcrt
fopen("D:\a\scoop-bucket\scoop-bucket\ctx-probe-b967d7f6d6fb4084b0c7a5ecfbafdf78\marker_msvcrt_fwdslash/",
"wb") -> FAILED errno=22 (Invalid argument)
-> nothing created on disk
msvcrt
fopen("D:\a\scoop-bucket\scoop-bucket\ctx-probe-b967d7f6d6fb4084b0c7a5ecfbafdf78\marker_msvcrt_backslash\",
"wb") -> FAILED errno=22 (Invalid argument)
-> nothing created on disk
msvcrt
fopen("D:\a\scoop-bucket\scoop-bucket\ctx-probe-b967d7f6d6fb4084b0c7a5ecfbafdf78\marker_msvcrt_plain",
"wb") -> OPENED
-> created: marker_msvcrt_plain [FILE, 0 bytes]
attributes=Archive
--- volume C:, probe directory
C:\Users\runneradmin\scoop\ctx-probe-44ee47e2feb14608a2f94c23526a11af ---
ucrtbase
fopen("C:\Users\runneradmin\scoop\ctx-probe-44ee47e2feb14608a2f94c23526a11af\marker_ucrtbase_fwdslash/",
"wb") -> FAILED errno=22 (Invalid argument)
-> nothing created on disk
ucrtbase
fopen("C:\Users\runneradmin\scoop\ctx-probe-44ee47e2feb14608a2f94c23526a11af\marker_ucrtbase_backslash\",
"wb") -> FAILED errno=22 (Invalid argument)
-> nothing created on disk
ucrtbase
fopen("C:\Users\runneradmin\scoop\ctx-probe-44ee47e2feb14608a2f94c23526a11af\marker_ucrtbase_plain",
"wb") -> OPENED
-> created: marker_ucrtbase_plain [FILE, 0 bytes]
attributes=Archive
msvcrt
fopen("C:\Users\runneradmin\scoop\ctx-probe-44ee47e2feb14608a2f94c23526a11af\marker_msvcrt_fwdslash/",
"wb") -> FAILED errno=22 (Invalid argument)
-> nothing created on disk
msvcrt
fopen("C:\Users\runneradmin\scoop\ctx-probe-44ee47e2feb14608a2f94c23526a11af\marker_msvcrt_backslash\",
"wb") -> FAILED errno=22 (Invalid argument)
-> nothing created on disk
msvcrt
fopen("C:\Users\runneradmin\scoop\ctx-probe-44ee47e2feb14608a2f94c23526a11af\marker_msvcrt_plain",
"wb") -> OPENED
-> created: marker_msvcrt_plain [FILE, 0 bytes]
attributes=Archive
=================== CONSEQUENCE: DOES A MARKER FILE BLOCK mkdir()?
===================
fopen("D:\a\scoop-bucket\scoop-bucket\ctx-probe-b967d7f6d6fb4084b0c7a5ecfbafdf78\web2c/",
"wb") -> FAILED errno=22 (Invalid argument)
'web2c' was not created - mkdir() would not be blocked here
fopen("C:\Users\runneradmin\scoop\ctx-probe-44ee47e2feb14608a2f94c23526a11af\web2c/",
"wb") -> FAILED errno=22 (Invalid argument)
'web2c' was not created - mkdir() would not be blocked here
=================== LUAMETATEX io.open() TRAILING-SEPARATOR TEST
===================
---
C:\Users\runneradmin\scoop\apps\context\current\tex\texmf-win64\bin\luametatex.exe
--luaonly <probe.lua> (probe directory
D:\a\scoop-bucket\scoop-bucket\ctx-probe-b967d7f6d6fb4084b0c7a5ecfbafdf78)
---
lua version: Lua 5.5
io.open("D:\\a\\scoop-bucket\\scoop-bucket\\ctx-probe-b967d7f6d6fb4084b0c7a5ecfbafdf78/lua_marker_fwdslash/",
'wb') -> nil
err=D:\a\scoop-bucket\scoop-bucket\ctx-probe-b967d7f6d6fb4084b0c7a5ecfbafdf78/lua_marker_fwdslash/:
Invalid argument errno=22
io.open("D:\\a\\scoop-bucket\\scoop-bucket\\ctx-probe-b967d7f6d6fb4084b0c7a5ecfbafdf78/lua_marker_backslash\\",
'wb') -> nil
err=D:\a\scoop-bucket\scoop-bucket\ctx-probe-b967d7f6d6fb4084b0c7a5ecfbafdf78/lua_marker_backslash\:
Invalid argument errno=22
io.open("D:\\a\\scoop-bucket\\scoop-bucket\\ctx-probe-b967d7f6d6fb4084b0c7a5ecfbafdf78/lua_marker_plain",
'wb') -> file (00007ff90cddda90) err=nil errno=nil
lua_marker_fwdslash -> nothing created on disk
lua_marker_backslash -> nothing created on disk
lua_marker_plain -> created: [FILE, 0 bytes] attributes=Archive
=================== SUMMARY ===================
Volume Probe Opened Created
------ ----- ------ -------
D: ucrtbase fopen(...'/') False False
D: msvcrt fopen(...'/') False False
C: ucrtbase fopen(...'/') False False
C: msvcrt fopen(...'/') False False
Reading: "Opened=True, Created=True" means this Windows CRT accepts a path
ending in "/" for writing and materialises a literal 0-byte file under the
separator-stripped name - the behaviour util-zip.lua's unzipdir() relies on
NOT happening (on POSIX the same open fails, making the write a no-op).
On Fri, Sep 4, 2026 at 2:25 PM Hans Hagen via ntg-context <
[email protected]> wrote:
> On 9/4/2026 10:25 AM, Jiri Bělka wrote:
> > Fix util-zip.lua's unzipdir() writing zip directory-marker entries as
> > 0-byte files, which then blocks mkdir() for the real files that belong
> > underneath them (reproducible on Windows; the same code path happens
> > to no-op harmlessly on POSIX, where opening a path ending in "/" for
> > writing simply fails).
> >
> > Upstream: ConTeXt LMTX, tex/context/base/mkiv/util-zip.lua, function
> > unzipdir - bundled (minified, concatenated with other core modules)
> > into bin/mtxrun.lua inside the small bootstrap archive
> > (context-<platform>.zip) that ships from lmtx.pragma-ade.com <http://
> > lmtx.pragma-ade.com>. This
> > patch targets that bundled bin/mtxrun.lua copy, applied with
> > `git apply -p1` from the app's install directory before the first
> > `mtx-install.lua --update` bootstrap run.
> >
> > How this was found
> > -------------------
> > The jirib/scoop-bucket "context" package's own Windows CI (GitHub
> > Actions, windows-latest) failed a fresh `scoop install context` with
> > mtxrun unable to find tex/texmf-context/web2c/contextcnf.lua, and
> > every mtxrun/context script lookup afterwards failing with "unknown
> > script ...". A diagnostic step added to that CI listed the tree after
> > bootstrap: tex/texmf-context had only ~10 entries, and every one of
> > them - including "web2c" - was a 0-byte *file* rather than a
> > directory (`Get-ChildItem` showed `-a----` file-mode entries where
> > directories were expected).
> >
> > To rule out a scoop-specific packaging mistake, the exact same
> > `mtx-install.lua --update` bootstrap was reproduced by hand, fresh,
> > directly against the real upstream context-linux-64.zip /
> > context-win64.zip / texmf-context.zip - on Linux it succeeded on the
> > first try (5723 real files under tex/texmf-context, web2c a real
> > directory); the identical code, same zip, same directory-marker
> > entries, failed only on Windows. Tracing unzipdir()/unzipfile() in the
> > bundled bin/mtxrun.lua explained the split: unzipfile() returns "" for
> > a directory-marker entry (0 compressed bytes), which Lua treats as
> > truthy, so unzipdir() proceeds to mkdirs()+savedata() for it. Writing
> > a file to a path ending in "/" apparently fails silently on POSIX
> > (harmless no-op there) but succeeds on Windows as a literal 0-byte
> > file with that name - which then blocks mkdir() for every real file
> > meant to live underneath it.
> >
> > This patch was applied to bin/mtxrun.lua and verified against a real,
> > fresh `scoop install context` on a GitHub Actions windows-latest
> > runner (jirib/scoop-bucket CI): the bootstrap now completes in ~2
> > minutes (previously it either corrupted the tree, or - with a
> > detect-and-retry workaround instead of this patch - took ~48 minutes
> > falling back to downloading ~6700 files individually), tex/texmf-context
> > contains the correct 5723 files with web2c as a real directory, and
> > `context --version` / `mtxrun --generate` both work.
> >
> > Reported upstream to [email protected] <mailto:[email protected]>.
> >
> > --- a/bin/mtxrun.lua
> > +++ b/bin/mtxrun.lua
> > @@ -16453,7 +16453,8 @@
> > for i=1,count do
> > local l=list[i]
> > local n=l.filename
> > - if not validate or validate(n) then
> > + if n:sub(-1) == "/" then
> > + elseif not validate or validate(n) then
> > local d=unzipfile(z,n)
> > if d then
> > local p=filejoin(path,n)
>
> more like (in util-zip.lua):
>
> if find(n,"/$") then
> -- mkdirs(dirname(n))
> elseif not validate or validate(n) then
>
> and then
>
> mtxrun --selfmerge
>
> but ... as I never saw this on windows, what file system are you using?
>
> Hans
>
> -----------------------------------------------------------------
> Hans Hagen | PRAGMA ADE
> Ridderstraat 27 | 8061 GH Hasselt | The Netherlands
> tel: 038 477 53 69 | www.pragma-ade.nl | www.pragma-pod.nl
> -----------------------------------------------------------------
>
> ___________________________________________________________________________________
> If your question is of interest to others as well, please add an entry to
> the Wiki!
>
> maillist : [email protected] /
> https://mailman.ntg.nl/mailman3/lists/ntg-context.ntg.nl
> webpage : https://www.pragma-ade.nl / https://context.aanhet.net (mirror)
> archive : https://github.com/contextgarden/context
> wiki : https://wiki.contextgarden.net
>
> ___________________________________________________________________________________
>
___________________________________________________________________________________
If your question is of interest to others as well, please add an entry to the
Wiki!
maillist : [email protected] /
https://mailman.ntg.nl/mailman3/lists/ntg-context.ntg.nl
webpage : https://www.pragma-ade.nl / https://context.aanhet.net (mirror)
archive : https://github.com/contextgarden/context
wiki : https://wiki.contextgarden.net
___________________________________________________________________________________