10 KiB
G-132 Draft the Tcl-on-windows errno catch-all report for manual submission
Status: proposed Scope: TEMP_REFERENCE/tcl-windows-errno-catchall-TICKET-DRAFT.md (submission-ready ticket text - STAGING ONLY, unversioned, and the agent does not submit it); this detail file (the tracked record: finding, verbatim reproducer, the submitted URL once the user files it, and the upstream disposition); src/bootsupport/AGENTS.md and src/make.tcl (the existing workaround and its warning - dated, not removed, if upstream fixes it) Goal: the Tcl-on-windows errno catch-all - every Win32 error above 267 surfacing as "invalid argument" - is written up as a submission-ready upstream report FOR THE DEVELOPER TO FILE MANUALLY, with a minimal reproducer and suggested fixes, and punkshell keeps the tracked record of the finding and whatever upstream decides, so the diagnosis is never re-derived the next time a high-numbered windows error appears as a nonsense message. Acceptance: the existing core.tcl-lang.org/tcl tracker is searched first and the result recorded here - an existing ticket is a valid outcome, and this goal then only links it and records its state; if unreported, a complete submission-ready ticket text is written to TEMP_REFERENCE/tcl-windows-errno-catchall-TICKET-DRAFT.md carrying title, summary, the minimal reproducer, the source citations (win/tclWinError.c - table extent 0..267 and the errorTable[1] fallback, verified present in both 8.6 and 9.x), the concrete windows code (ERROR_USER_MAPPED_FILE 1224), the cross-platform observation that the same 'file copy -force' succeeds on unix, and the candidate fixes with their trade-offs; THE AGENT DOES NOT SUBMIT IT - no account use, no web form, no API, no mail - submission is the developer's action alone, and the goal is not complete until the developer has submitted or declined; because TEMP_REFERENCE is ignored by both VCS and is one 'git clean -xdf' from gone, the reproducer and the source citations are ALSO carried verbatim in this tracked detail file, so the draft file is a convenience for pasting and never the only copy; once the developer submits, the ticket URL and the upstream response are recorded here with a disposition (fixed-in-release / declined / duplicate / superseded), and if fixed, the fixing release is named so the make.tcl workaround and the src/bootsupport/AGENTS.md warning can be dated; and the punkshell-side workaround is NOT removed by this goal - it must keep working on every runtime punkshell supports regardless of what upstream does.
Context
Found 2026-07-27 while diagnosing a make.tcl bootsupport failure that had recurred three
times: replacing the zip-based punk::mix::templates modpod in src/bootsupport failed
with
error copying "<src>" to "<dst>": invalid argument
The message is a dead end - it reads as a bad argument, and it names neither the real cause nor a remedy. Two earlier attempts at characterising it (recorded in commit messages and in goals/archive/G-124-punkzip-reader.md) were both wrong in different ways, precisely because the message carries no information.
The punkshell-side cause is understood and worked around (see the Finding below and src/bootsupport/AGENTS.md): a zipfs mount memory-maps its archive, and windows refuses to overwrite a file with a user-mapped section open. That part is ours and is fixed.
What remains is upstream's: Tcl converts EVERY windows error above 267 to EINVAL, so an
entire class of real, common file-operation failures arrives as the same uninformative
message. That is worth reporting, and this goal exists so that the report is drafted
properly and the outcome is recorded - not so that an agent files it.
Finding (tracked copy - TEMP_REFERENCE is unversioned)
Verified 2026-07-27 against TEMP_REFERENCE/tcl9 (9.1b1) and
TEMP_REFERENCE/androwish/jni/tcl (8.6.10). BOTH generations carry byte-identical logic.
win/tclWinError.c holds errorTable[], whose last entry is index 267
(ENOTDIR /* ERROR_DIRECTORY 267 */). The converter is:
void
Tcl_WinConvertError(unsigned errCode)
{
if ((unsigned)errCode >= sizeof(errorTable)/sizeof(errorTable[0])) {
errCode -= WSAEWOULDBLOCK;
if ((unsigned)errCode >= sizeof(wsaErrorTable)/sizeof(wsaErrorTable[0])) {
Tcl_SetErrno(errorTable[1]);
} else {
Tcl_SetErrno(wsaErrorTable[errCode]);
}
} else {
Tcl_SetErrno(errorTable[errCode]);
}
}
errorTable[1] is EINVAL (/* ERROR_INVALID_FUNCTION 1 */). So any windows error code
above 267 that is not in the WSA range collapses to EINVAL -> "invalid argument". The
affected set is not exotic:
303 ERROR_DELETE_PENDING file is in the process of being deleted
1224 ERROR_USER_MAPPED_FILE cannot be performed on a file with a user-mapped
section open
1920 ERROR_CANT_ACCESS_FILE the file cannot be accessed by the system
1921 ERROR_CANT_RESOLVE_FILENAME the name of the file cannot be resolved
4390 ERROR_NOT_A_REPARSE_POINT not a reparse point
The specific case that surfaced it: tclZipfs.c maps a mounted archive with
CreateFileMappingW + MapViewOfFile (9.1b1: :1867 and :1875; UnmapViewOfFile at :1475),
so any process holding a zipfs mount holds a user-mapped section on that file, and windows
raises 1224 for an in-place overwrite.
Cross-platform observation worth including in the report: the same file copy -force over a
memory-mapped file SUCCEEDS on unix. So the failure is windows-only and silent about it,
which makes the misleading errno costlier than it first appears.
Measured behaviour, same machine, Tcl 9.0.3:
file copy -force over the archive, not mounted -> ok
file copy -force over the archive, zipfs-mounted -> invalid argument
file copy -force over the archive, unmounted -> ok
file delete of the archive, while mounted -> ok <-- unlink is permitted
delete-then-place while mounted -> ok, and the holder keeps
reading its own mapping
Reproducer (tracked copy)
Self-contained, no punkshell, windows only. Any zip will do - a kit or a zip-based .tm
modpod is convenient.
set f mycopy.zip
file copy -force somearchive.zip $f
tcl::zipfs::mount $f probe
catch {file copy -force someotherfile $f} e
puts $e ;# error copying ...: invalid argument
;# actual cause: ERROR_USER_MAPPED_FILE (1224), unrepresented in errorTable
tcl::zipfs::unmount probe
Expected after a fix: an errno that distinguishes this from a caller error - the message should not claim the arguments were invalid.
Approach
- Search the tracker BEFORE drafting. This is old code present in 8.6 and 9.x, so a prior report is entirely plausible and finding one is a complete result for this goal.
- Draft to
TEMP_REFERENCE/tcl-windows-errno-catchall-TICKET-DRAFT.mdin submission-ready form - a developer should be able to paste it without editing. - Keep the claim narrow and defensible: the REPORT is about the errno conversion, which is
plainly a defect. The
file copy -forceportability difference is context in the same ticket, not a second demand - upstream may reasonably decline to change copy semantics (atomicity, ACL inheritance), and bundling it as a demand weakens the part that is clearly wrong. - Offer candidate fixes with trade-offs rather than insisting on one: extend the table;
give the out-of-range branch a better default than
EINVALfor the sharing/locking family (EACCESis defensible,EBUSYarguably closer); or expose the raw windows code so callers can diagnose. Deciding among these is upstream's call.
Alternatives considered
- File it directly from an agent - rejected, and this is the point of the goal's shape: the developer submits. Agent-filed reports on a project's public tracker are the developer's reputation and correspondence to own, and a ticket needs someone who can answer follow-up questions.
- Fix it locally and say nothing - rejected: the workaround is in punkshell's build tooling only, and every other Tcl user on windows keeps meeting "invalid argument" for a class of errors that has nothing to do with arguments.
- Submit a patch rather than a report - deferred, not rejected. It is a table and the change is small, but choosing the right errno per class is a design conversation, and a report that names the candidates invites that conversation without presuming its outcome. If the developer prefers the patch aim, this goal's Acceptance changes rather than the finding.
Notes
- PRECEDENT: G-039 (achieved 2026-07-12) filed a Tcl 9 windows console defect the same way -
agent drafted the full text with repro scripts to
TEMP_REFERENCE/, the developer submitted, and the goal recorded the URL (https://core.tcl-lang.org/tcl/tktview/f10d91c2d3). That draft elided the scripts from the web submission; whether to do the same here is the developer's call at submission time, not something this goal presumes. - The punkshell workaround this goal does NOT touch:
::punkboot::replace_possibly_mapped_filefalls back from overwrite to delete-then-place, and::punkboot::mapped_file_hintexplains the catch-all when a copy genuinely fails. Both live insrc/make.tcland are documented insrc/bootsupport/AGENTS.md. They must keep working on every runtime punkshell supports regardless of upstream, so an upstream fix dates them rather than deletes them. - Related: G-110 - it studies caching of EXTRACTED shared libs, where extraction today is done by zipfs/vfs::zip. Anything that holds a zipfs mount holds a mapping, so the same windows constraint applies to any scheme that wants to replace a mounted archive in place.
- The
invalid argumentreading applies to ANY windows file operation in Tcl, not just copies. Treat it as "some windows error above 267" wherever it appears; that guidance is recorded insrc/bootsupport/AGENTS.mdbecause that is where it bit, but it is general.