Browse Source

add goal G-028: name the process locking a file on build deploy failures

Motivated by a make.tcl project deploy failure ("could not delete target
binary") where the locker turned out to be 7-Zip inspecting the exe -
identifying it took several dead ends because Windows has no cheap
file-to-process reverse lookup (name matching and Process.Modules scans
both miss plain file handles; openfiles needs a pre-enabled global flag;
handle.exe is external and elevation-bound).

G-028: a punkboot::utils helper returns the locking processes via the
Windows Restart Manager API (the installers-grade answer to "who is
blocking this file"), with twapi or cffi required lazily at call time -
punkboot::utils stays pure Tcl per the bootsupport no-compiled-extensions
contract, and unavailability degrades to the existing message. make.tcl
kit deploy failures append the locker report when determinable. Recurring
customer: G-023 versioned/dev binaries rewritten every build.

Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
master
Julian Noble 2 weeks ago
parent
commit
64fa9a63a7
  1. 6
      GOALS.md
  2. 67
      goals/G-028-file-locker-identification.md

6
GOALS.md

@ -210,3 +210,9 @@ Scope: src/modules/punk/mix/commandset/project-999999.0a1.0.tm (project.new push
Detail: goals/G-027-derived-project-pull-updates.md
Goal: a project generated from a punkshell layout can pull infrastructure updates (make.tcl/build.tcl, bootsupport modules and libs, layout template payloads) from its originating punkshell project by running one command inside the derived project - replacing the current push model (`dev project.new -force 1 -update 1` run from punkshell) - with install provenance robust to derived-project workdir moves (not local relative paths alone), VCS-state awareness on both ends, and a recorded decision on pulling from remote sources.
Acceptance: one documented command run inside a derived project updates its punkshell-derived infrastructure from the source punkshell project; the update still works after the derived project's working directory has been moved (proven by moving a scratch derived project and pulling); VCS integration on both ends: the pull applies the G-026 clean-checkout policy to the punkshell source, and reports (or refuses per option) when target files it would overwrite carry uncommitted local modifications in the derived project's git/fossil checkout; .punkcheck records remain the provenance basis (updates are recorded and unchanged targets skipped, as with existing punkcheck-tracked installs); the push flow keeps working until explicitly retired; the remote-pull question (updating from a remote punkshell repository rather than a local checkout) has a recorded design decision - implemented, or deferred with rationale in the detail file.
### G-028 [proposed] Name the process locking a file when builds cannot replace a target
Scope: src/modules/punkboot/utils-999999.0a1.0.tm (locker-report helper), src/make.tcl (kit deploy failure reporting)
Detail: goals/G-028-file-locker-identification.md
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).

67
goals/G-028-file-locker-identification.md

@ -0,0 +1,67 @@
# 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)
Acceptance: as in root GOALS.md index (canonical).
## 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 <path>) returning a list of
{pid <n> name <procname> ...} 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).
Loading…
Cancel
Save