# 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 "" to "": 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.md` in 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 -force` portability 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 `EINVAL` for the sharing/locking family (`EACCES` is defensible, `EBUSY` arguably 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_file` falls back from overwrite to delete-then-place, and `::punkboot::mapped_file_hint` explains the catch-all when a copy genuinely fails. Both live in `src/make.tcl` and are documented in `src/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 argument` reading 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 in `src/bootsupport/AGENTS.md` because that is where it bit, but it is general.