gastownhall/beads · warning
procid: close process handle: %w
Error message
procid: close process handle: %w
What it means
Handle.Close calls windows.CloseHandle on the stored process handle and wraps any failure. This almost always means the handle value is invalid — typically a double-Close or use-after-Close, since Close sets h.process = 0 afterward, which normally makes repeat Close a no-op.
Source
Thrown at internal/procid/procid_windows.go:101
if err := windows.TerminateProcess(h.process, 1); err != nil {
if _, exitedErr := tokenForProcess(h.process); errors.Is(exitedErr, errProcessExited) {
return nil
}
return fmt.Errorf("procid: terminate process: %w", err)
}
return nil
}
func (h *Handle) Kill() error { return h.Signal(os.Kill) }
func (h *Handle) Close() error {
if h.process == 0 {
return nil
}
err := windows.CloseHandle(h.process)
h.process = 0
if err != nil {
return fmt.Errorf("procid: close process handle: %w", err)
}
return nil
}
func (h *Handle) verify() error {
current, err := tokenForProcess(h.process)
if err != nil {
return err
}
if current != h.token {
return fmt.Errorf("procid: process no longer matches token")
}
return nil
}
func openProcess(pid int, access uint32) (windows.Handle, error) {
return windows.OpenProcess(access, false, uint32(pid))
}View on GitHub (pinned to 71377f2769)
Solutions
- Guard Close with sync.Once or a mutex so it runs exactly once
- Never copy the *Handle by value; always pass *procid.Handle by pointer
- Ignore or downgrade INVALID_HANDLE_VALUE-class errors on idempotent shutdown paths
- Always obtain Handle via procid.Open; don't construct one manually
Example fix
// before
var closeOnce sync.Once
defer h.Close()
defer h.Close() // second call races
// after
var closeOnce sync.Once
closeFn := func() { _ = h.Close() }
defer closeOnce.Do(closeFn)
defer closeOnce.Do(closeFn) Defensive patterns
Strategy: try-catch
Validate before calling
// Guard against double close at call sites
var closed bool
func shutdown(h *procid.Handle) error {
if closed { return nil }
closed = true
return h.Close()
} Type guard
func safeClose(h *procid.Handle) error {
if h == nil {
return nil
}
return h.Close() // Close itself is idempotent when h.process == 0
} Try / catch
if err := h.Close(); err != nil {
// invalid handle on close is rarely actionable; log and continue
log.Printf("close process handle: %v", err)
} Prevention
- Close each Handle exactly once via defer at the Open site
- Use sync.Once for shutdown paths with multiple triggers
- Never copy Handle structs by value
- Treat Close errors as warnings, not fatal, since the goal is resource release
When it happens
Trigger: Calling Close twice concurrently (race: both see h.process != 0); closing a Handle whose underlying handle was invalidated externally; passing a zero-value Handle built outside Open.
Common situations: Concurrent shutdown paths both calling Close/deferred Close; copying the Handle struct by value so two copies own the same raw handle.
Related errors
- lock already held by another process
- procid: get process exit code: %w
- lock busy: held by another process
- lock already held by another process
- procid: process has exited
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/700a5446d18d2320.
Report an issue: GitHub.