gastownhall/beads · error

procid: get process times: %w

Error message

procid: get process times: %w

What it means

After confirming the process is still active, tokenForProcess reads the process creation time via windows.GetProcessTimes to build a unique, PID-reuse-resistant token. If GetProcessTimes fails, the error is wrapped and propagated to whichever public API (Capture/Verify/Open/Signal/verify) initiated the token mint.

Source

Thrown at internal/procid/procid_windows.go:134

func openProcess(pid int, access uint32) (windows.Handle, error) {
	return windows.OpenProcess(access, false, uint32(pid))
}

func tokenForProcess(process windows.Handle) (Token, error) {
	// An open handle keeps a terminated process's PID resolvable and its
	// creation time readable, so check liveness explicitly before minting a
	// token for it.
	var code uint32
	if err := windows.GetExitCodeProcess(process, &code); err != nil {
		return "", fmt.Errorf("procid: get process exit code: %w", err)
	}
	if code != stillActive {
		return "", errProcessExited
	}
	var created, exited, kernel, user windows.Filetime
	if err := windows.GetProcessTimes(process, &created, &exited, &kernel, &user); err != nil {
		return "", fmt.Errorf("procid: get process times: %w", err)
	}
	value := uint64(created.HighDateTime)<<32 | uint64(created.LowDateTime)
	return Token("windows-v1:" + strconv.FormatUint(value, 10)), nil
}

// IsProcessGone reports whether err means the referenced process no longer
// exists.
func IsProcessGone(err error) bool {
	return errors.Is(err, windows.ERROR_INVALID_PARAMETER) ||
		errors.Is(err, errProcessExited)
}

View on GitHub (pinned to 71377f2769)

Solutions

  1. Verify the handle has PROCESS_QUERY_LIMITED_INFORMATION (procid.Capture already requests it)
  2. Check errors.Is(err, windows.ERROR_ACCESS_DENIED); run elevated or skip processes you cannot query
  3. Retry the Capture once after a short delay in case of transient handle invalidation
  4. Skip token capture for processes outside your ownership boundary instead of failing the whole batch

Example fix

// before
tok, err := procid.Capture(pid)
if err != nil { return err }
// after
tok, err := procid.Capture(pid)
if err != nil {
    if errors.Is(err, windows.ERROR_ACCESS_DENIED) {
        return nil // cannot token-capture protected process; skip
    }
    return err
}
Defensive patterns

Strategy: retry

Validate before calling

if !canQuery(pid) {
    return nil // cannot read process times; skip capture
}

Type guard

func tokenCapturable(pid int) bool {
    h, err := windows.OpenProcess(windows.PROCESS_QUERY_LIMITED_INFORMATION, false, uint32(pid))
    if err != nil { return false }
    _ = windows.CloseHandle(h)
    return true
}

Try / catch

var tok procid.Token
var err error
for i := 0; i < 2; i++ {
    tok, err = procid.Capture(pid)
    if err == nil || procid.IsProcessGone(err) || errors.Is(err, windows.ERROR_ACCESS_DENIED) {
        break
    }
    time.Sleep(50 * time.Millisecond)
}

Prevention

When it happens

Trigger: GetProcessTimes failing on a freshly opened handle — access denied on protected/system processes, or the handle was opened without query rights.

Common situations: Capturing tokens for SYSTEM or protected processes from a non-elevated daemon; handle rights reduced by third-party code; race where the handle was closed concurrently.

Related errors


AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30). Data as JSON: /api/errors/9c05ac6d1340d491. Report an issue: GitHub.