You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

5.1 KiB

G-028 Name the process locking a file when builds cannot replace a target

Status: proposed Scope: src/modules/punkboot/utils-999999.0a1.0.tm (locker-report helper), src/make.tcl (kit deploy failure reporting) Goal: when a build cannot delete/overwrite a target file (typically a built executable held open by another program), the failure message names the locking process(es) - via a punkboot::utils helper using the Windows Restart Manager API through optionally-available twapi or cffi, degrading cleanly to the current message when the API or bindings are unavailable or on other platforms. Acceptance: on Windows with twapi or cffi loadable, a punkboot::utils proc given a file path returns the locking processes (at least pid and process name; empty list when unlocked); punkboot::utils itself stays pure Tcl - the binary binding is required lazily at call time and its absence yields a clean 'unavailable' result, not an error (bootsupport must not gain a compiled-extension dependency); make.tcl kit deploy failures include the locker report when determinable (e.g. "could not delete target binary ... in use by: 7zFM.exe (pid 1234)"); non-Windows platforms and binding-less environments produce the existing message unchanged; verified against a deliberately held handle (documented manual verification acceptable).

Context

Motivating incident (2026-07-07, tomlish project): make.tcl project failed with "could not delete target binary at .../bin/tomlish.exe" and no further information. The lock turned out to be 7-Zip holding the exe open for archive inspection - but identifying that took several dead ends, because Windows has no cheap reverse lookup from file to holding process:

  • tasklist / Get-Process by name only finds processes running as the file.
  • Scanning Process.Modules only sees files mapped as images (loaded EXE/DLLs), not plain open handles, and silently skips inaccessible processes.
  • Full handle enumeration (NtQuerySystemInformation) needs elevation and per-handle queries; openfiles requires a globally pre-enabled tracking flag; Sysinternals handle.exe / Resource Monitor are external/manual.

The Restart Manager API (RmStartSession/RmRegisterResources/RmGetList) is the practical programmatic answer: it is what installers use for the "these applications are using this file" dialog, does not require elevation for the common cases, and directly answers "who is blocking my replace/delete". It is callable from Tcl via twapi or cffi, both already shipped in the punk Windows kit.

Deploy-target locks will recur - G-023's versioned/dev binary outputs are rewritten every build, and a running or inspected punk8/9-dev.exe is exactly the kind of file this fails on.

Approach

  • punkboot::utils helper (e.g. file_lockers ) returning a list of {pid name ...} records, empty when unlocked.
  • Constraint: bootsupport carries no compiled extensions (src/bootsupport AGENTS.md). punkboot::utils stays pure Tcl; twapi (RmStartSession et al via its API coverage) or cffi (direct Rstrtmgr.dll binding) is package-required lazily inside the helper, from the environment's auto_path. Unavailable binding, non-Windows platform, or API failure all yield a clean 'unavailable' result the caller renders as the existing message.
  • make.tcl kit deploy (and any other replace-target failure sites worth covering) appends the locker report to its failure output when available.
  • Prefer twapi if its Restart Manager coverage suffices; cffi binding as the fallback implementation choice - record the choice here when made.

Alternatives considered

  • Full handle-table enumeration (NtQuerySystemInformation) - rejected: elevation-dependent, expensive, and reimplements what Restart Manager already provides for the blocking-file case.
  • Shelling out to Sysinternals handle.exe - rejected: external download, license prompt on first run, elevation required; not dependable build tooling.
  • openfiles - rejected: requires a pre-enabled global tracking flag with a standing performance cost.

Notes

  • Related: G-023 (versioned binary deploys are the recurring customer), G-020 (same optionally-available twapi/cffi pattern for platform features), punkboot::utils chicken-and-egg ordering rules in src/bootsupport/AGENTS.md apply when adding the helper.
  • Unix analogues (lsof/fuser) could slot behind the same helper interface later; out of scope for acceptance (Windows is where the pain is and where the API answer is non-obvious).
  • Sibling candidate parked here 2026-07-14 (G-030 follow-up): make.tcl's pre-deploy process sweep matches processes by executable NAME only (tasklist gives no path), so an unrelated same-named executable elsewhere on the system can be killed - the code's own TODO. The sweep now excludes the build's own pid and the self-build target kit is skipped entirely, but other-instance misidentification remains; full-path process matching (powershell get-process, or the same Restart-Manager-adjacent infrastructure this goal introduces) is the fix, and a locker-report helper that returns paths would serve both. (G-030 achieved 2026-07-14 - goals/archive/G-030-maketcl-punkargs.md)