{"record":{"id":"e1ca107fafbd915e","repo":"astrid-runtime/astrid","slug":"windows-named-pipe-endpoint-disappeared-while-wait","errorCode":null,"errorMessage":"Windows named-pipe endpoint disappeared while waiting","messagePattern":"Windows named-pipe endpoint disappeared while waiting","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/astrid-core/src/local_transport/windows.rs","lineNumber":197,"sourceCode":"\nasync fn wait_for_pipe_availability(pipe_name: &OsStr, wait: Duration) -> io::Result<()> {\n    let encoded = wide_nul(pipe_name);\n    let milliseconds = u32::try_from(wait.as_millis())\n        .map_err(|_| io::Error::other(\"named-pipe wait duration overflow\"))?\n        .max(1);\n    // `WaitNamedPipeW` is synchronous, so isolate it from the async worker.\n    // Each call is capped at 50 ms: cancelling the outer future stops all\n    // retries and leaves at most one short detached blocking wait.\n    tokio::task::spawn_blocking(move || {\n        let ready = unsafe { WaitNamedPipeW(encoded.as_ptr(), milliseconds) };\n        if ready != 0 {\n            return Ok(());\n        }\n        let error = io::Error::last_os_error();\n        match error.raw_os_error().map(i32::cast_unsigned) {\n            // A bounded timeout is the backoff between open attempts.\n            Some(ERROR_SEM_TIMEOUT | ERROR_PIPE_BUSY) => Ok(()),\n            Some(ERROR_FILE_NOT_FOUND) => Err(io::Error::new(\n                io::ErrorKind::NotFound,\n                \"Windows named-pipe endpoint disappeared while waiting\",\n            )),\n            Some(ERROR_ACCESS_DENIED) => Err(io::Error::new(\n                io::ErrorKind::PermissionDenied,\n                \"Windows named-pipe endpoint denied access while waiting\",\n            )),\n            _ => Err(error),\n        }\n    })\n    .await\n    .map_err(|error| io::Error::other(format!(\"named-pipe wait task failed: {error}\")))?\n}\n\npub(super) async fn connect_outcome(path: &Path) -> io::Result<ConnectOutcome> {\n    match connect(path).await {\n        Ok(stream) => Ok(ConnectOutcome::Connected(stream)),\n        Err(error) if error.kind() == io::ErrorKind::NotFound => Ok(ConnectOutcome::Absent),","sourceCodeStart":179,"sourceCodeEnd":215,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-core/src/local_transport/windows.rs#L179-L215","documentation":"While a Windows named-pipe client is waiting for a busy server endpoint to become available, the retry loop checks the pipe state; ERROR_FILE_NOT_FOUND means the pipe endpoint no longer exists at all, so further waiting is pointless. Astrid surfaces this as io::ErrorKind::NotFound. It typically means the server stopped listening (pipe server closed) while the client was backing off between open attempts.","triggerScenarios":"open_client_with_retry -> wait_for_pipe_availability observes GetLastError == ERROR_FILE_NOT_FOUND for the named pipe path, i.e. the pipe was busy/denied earlier but is now gone before a successful open.","commonSituations":"The Astrid server process exited or cancelled its CreateNamedPipe listener while a client was retrying; a service restart raced with a connecting client; wrong pipe name that briefly existed from another process.","solutions":["Verify the server process is still running and re-creating its named-pipe listener; restart it if it died","Retry the connection from scratch (re-resolve the endpoint) rather than reusing a stale pipe path","Confirm the pipe name matches exactly what the server creates (case and \\\\pipe\\ prefix)"],"exampleFix":"// before\nlet stream = client.open_with_retry(&path).await?; // fails if server dies mid-retry\n// after\nlet stream = match client.open_with_retry(&path).await {\n    Err(e) if e.kind() == io::ErrorKind::NotFound => {\n        ensure_server_running()?;\n        client.open_with_retry(&path).await?\n    },\n    other => other?,\n};","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"match client.open_with_retry(&pipe).await {\n    Err(e) if e.kind() == std::io::ErrorKind::NotFound => {\n        ensure_server_alive()?;            // pipe vanished: restart server / re-resolve\n        client.open_with_retry(&pipe).await?\n    },\n    other => other?,\n}","preventionTips":["Monitor the server process health so pipe listeners are recreated promptly on crash","Use exact, unique pipe names to avoid collisions and stale endpoints","Bound total retry time and fail with a clear 'server went away' message"],"tags":["windows","named-pipes","ipc","io"],"backgroundTag":"file-not-found","analyzedSha":"affd8760f44190dbdfbec23403f4c4b642c33112","analyzedAt":"2026-09-09T21:28:12.402Z","contentChangedAt":"2026-09-09T21:28:12.402Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}