gastownhall/beads · error

procid: get process exit code: %w

Error message

procid: get process exit code: %w

What it means

tokenForProcess calls windows.GetExitCodeProcess to check liveness before minting a token; if that Win32 call fails the error is wrapped with this message. It is reachable from Capture, Verify, Open, Signal and Handle.verify, so any of those public APIs can surface it.

Source

Thrown at internal/procid/procid_windows.go:127

		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))
}

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. Ensure the process handle was opened with PROCESS_QUERY_LIMITED_INFORMATION (always use procid.Open/Capture rather than raw OpenProcess)
  2. Check errors.Is(err, windows.ERROR_ACCESS_DENIED) and elevate if needed
  3. Avoid sharing/invalidating the underlying handle outside procid; don't Close handles concurrently with Signal/Verify
  4. If the process is gone, prefer procid.IsProcessGone / errProcessExited handling over treating this as a hard failure

Example fix

// before
proc, _ := windows.OpenProcess(windows.SYNCHRONIZE, false, uint32(pid))
tok, err := procid.Verify(pid, known) // unrelated handle rights cause query failures elsewhere
// after
proc, err := windows.OpenProcess(windows.PROCESS_QUERY_LIMITED_INFORMATION, false, uint32(pid))
if err != nil { return err }
Defensive patterns

Strategy: validation

Validate before calling

// Ensure handles come from procid APIs that request the right access
proc, err := windows.OpenProcess(windows.PROCESS_QUERY_LIMITED_INFORMATION, false, uint32(pid))
if err != nil {
    return fmt.Errorf("cannot query pid %d: %w", pid, err)
}
_ = windows.CloseHandle(proc)

Type guard

func canQuery(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

tok, err := procid.Capture(pid)
if err != nil {
    if errors.Is(err, windows.ERROR_ACCESS_DENIED) {
        return nil // skip unqueryable process
    }
    return err
}

Prevention

When it happens

Trigger: Calling any procid API with a handle that lacks PROCESS_QUERY_LIMITED_INFORMATION access, or a handle that has become invalid between Open and the token query.

Common situations: Constructing handles with narrower access rights than the library requires; handle closed concurrently while another goroutine queries it; mixing procid with other code that closes raw windows.Handle values.

Related errors


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