{"record":{"id":"ed140a8cdced243c","repo":"vercel/turborepo","slug":"no-threads-found-for-process-process-id","errorCode":null,"errorMessage":"no threads found for process {process_id}","messagePattern":"no threads found for process (.+?)","errorType":"exception","errorClass":"std::io::Error","httpStatus":null,"severity":"warning","filePath":"crates/turborepo-process/src/job_object.rs","lineNumber":440,"sourceCode":"        tpDeltaPri: 0,\n        dwFlags: 0,\n    };\n\n    let mut found_thread = false;\n    let mut has_entry = unsafe { Thread32First(snapshot, &mut entry) } != 0;\n    while has_entry {\n        if entry.th32OwnerProcessID == process_id {\n            found_thread = true;\n            resume_thread(entry.th32ThreadID)?;\n        }\n\n        has_entry = unsafe { Thread32Next(snapshot, &mut entry) } != 0;\n    }\n\n    if found_thread {\n        Ok(())\n    } else {\n        Err(io::Error::new(\n            io::ErrorKind::NotFound,\n            format!(\"no threads found for process {process_id}\"),\n        ))\n    }\n}\n\nfn resume_thread(thread_id: u32) -> io::Result<()> {\n    let thread_handle = unsafe { OpenThread(THREAD_SUSPEND_RESUME, 0, thread_id) };\n    if thread_handle.is_null() {\n        return Err(io::Error::last_os_error());\n    }\n\n    let resume_result = unsafe { ResumeThread(thread_handle) };\n    let resume_error = (resume_result == u32::MAX).then(io::Error::last_os_error);\n    let close_result = unsafe { CloseHandle(thread_handle) };\n\n    if let Some(err) = resume_error {\n        return Err(err);","sourceCodeStart":422,"sourceCodeEnd":458,"githubUrl":"https://github.com/vercel/turborepo/blob/f9245100cf0d31d96628804ead485f6bf226e55a/crates/turborepo-process/src/job_object.rs#L422-L458","documentation":"On Windows, resume_process (job_object.rs:440 region) walks a Thread32First/Thread32Next snapshot and resumes (OpenThread + ResumeThread) every thread whose th32OwnerProcessID matches. If the walk completes with no thread owned by that process, it returns NotFound 'no threads found for process {id}'. The process has no threads left, i.e. it already exited between suspension and resume.","triggerScenarios":"Resuming a process in a job object after it exited while suspended or during teardown — e.g. the Ctrl+C/shutdown path resuming children that have already died, or a child that crashed immediately after start.","commonSituations":"Very short-lived child processes exiting before resume Force-killed trees during cancellation Benign races at shutdown; the process is gone either way","solutions":["Treat NotFound from the resume path as 'already exited' — log and continue teardown instead of failing the shutdown","If it fires on every run, check why the target dies immediately (run the command outside turbo, look at its exit status)","Avoid suspending/resuming processes that you also allow to self-exit quickly"],"exampleFix":"// before\nresume_process(pid)?;\n// after — already-exited is success during teardown\nif let Err(e) = resume_process(pid) {\n    if e.kind() != std::io::ErrorKind::NotFound {\n        return Err(e);\n    }\n    tracing::debug!(pid, \"process exited before resume\");\n}","handlingStrategy":"fallback","validationCode":"// before resuming, check the process still exists\nif !proc_exists(pid) { return Ok(()); } // already gone\nfn proc_exists(pid: u32) -> bool {\n    std::path::Path::new(&format!(\"/proc/{pid}\")).exists() // unix; use OpenProcess on windows\n}","typeGuard":null,"tryCatchPattern":"// NotFound during resume == process exited: swallow and continue teardown\nif let Err(e) = resume_process(pid) {\n    if e.kind() != std::io::ErrorKind::NotFound { return Err(e); }\n    tracing::debug!(pid, \"already exited before resume\");\n}","preventionTips":["Treat thread-snapshot races at teardown as expected","Don't gate shutdown success on resuming processes that may have died"],"tags":["windows","process","threads","job-object","teardown"],"backgroundTag":"process-already-exited","analyzedSha":"f9245100cf0d31d96628804ead485f6bf226e55a","analyzedAt":"2026-08-17T10:46:15.696Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}