{"record":{"id":"5330de5b3452bf7d","repo":"gastownhall/beads","slug":"procid-get-process-exit-code-w","errorCode":null,"errorMessage":"procid: get process exit code: %w","messagePattern":"procid: get process exit code: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/procid/procid_windows.go","lineNumber":127,"sourceCode":"\t\treturn err\n\t}\n\tif current != h.token {\n\t\treturn fmt.Errorf(\"procid: process no longer matches token\")\n\t}\n\treturn nil\n}\n\nfunc openProcess(pid int, access uint32) (windows.Handle, error) {\n\treturn windows.OpenProcess(access, false, uint32(pid))\n}\n\nfunc tokenForProcess(process windows.Handle) (Token, error) {\n\t// An open handle keeps a terminated process's PID resolvable and its\n\t// creation time readable, so check liveness explicitly before minting a\n\t// token for it.\n\tvar code uint32\n\tif err := windows.GetExitCodeProcess(process, &code); err != nil {\n\t\treturn \"\", fmt.Errorf(\"procid: get process exit code: %w\", err)\n\t}\n\tif code != stillActive {\n\t\treturn \"\", errProcessExited\n\t}\n\tvar created, exited, kernel, user windows.Filetime\n\tif err := windows.GetProcessTimes(process, &created, &exited, &kernel, &user); err != nil {\n\t\treturn \"\", fmt.Errorf(\"procid: get process times: %w\", err)\n\t}\n\tvalue := uint64(created.HighDateTime)<<32 | uint64(created.LowDateTime)\n\treturn Token(\"windows-v1:\" + strconv.FormatUint(value, 10)), nil\n}\n\n// IsProcessGone reports whether err means the referenced process no longer\n// exists.\nfunc IsProcessGone(err error) bool {\n\treturn errors.Is(err, windows.ERROR_INVALID_PARAMETER) ||\n\t\terrors.Is(err, errProcessExited)\n}","sourceCodeStart":109,"sourceCodeEnd":145,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/procid/procid_windows.go#L109-L145","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Ensure the process handle was opened with PROCESS_QUERY_LIMITED_INFORMATION (always use procid.Open/Capture rather than raw OpenProcess)","Check errors.Is(err, windows.ERROR_ACCESS_DENIED) and elevate if needed","Avoid sharing/invalidating the underlying handle outside procid; don't Close handles concurrently with Signal/Verify","If the process is gone, prefer procid.IsProcessGone / errProcessExited handling over treating this as a hard failure"],"exampleFix":"// before\nproc, _ := windows.OpenProcess(windows.SYNCHRONIZE, false, uint32(pid))\ntok, err := procid.Verify(pid, known) // unrelated handle rights cause query failures elsewhere\n// after\nproc, err := windows.OpenProcess(windows.PROCESS_QUERY_LIMITED_INFORMATION, false, uint32(pid))\nif err != nil { return err }","handlingStrategy":"validation","validationCode":"// Ensure handles come from procid APIs that request the right access\nproc, err := windows.OpenProcess(windows.PROCESS_QUERY_LIMITED_INFORMATION, false, uint32(pid))\nif err != nil {\n    return fmt.Errorf(\"cannot query pid %d: %w\", pid, err)\n}\n_ = windows.CloseHandle(proc)","typeGuard":"func canQuery(pid int) bool {\n    h, err := windows.OpenProcess(windows.PROCESS_QUERY_LIMITED_INFORMATION, false, uint32(pid))\n    if err != nil { return false }\n    _ = windows.CloseHandle(h)\n    return true\n}","tryCatchPattern":"tok, err := procid.Capture(pid)\nif err != nil {\n    if errors.Is(err, windows.ERROR_ACCESS_DENIED) {\n        return nil // skip unqueryable process\n    }\n    return err\n}","preventionTips":["Always obtain process handles through procid.Capture/Open, which request the required access","Do not share raw windows.Handle values with code that may close them","Check access rights early rather than at token-mint time","Run token-minting code as the same user as the target processes where possible"],"tags":["windows","process","handle","permissions"],"backgroundTag":"process-query-access-denied","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}