astrid-runtime/astrid · error

Windows named-pipe endpoint denied access while waiting

Error message

Windows named-pipe endpoint denied access while waiting

What it means

During the Windows named-pipe open retry loop, ERROR_ACCESS_DENIED from the pipe state check means the endpoint exists but the client is not permitted to access it (the pipe server's ACL denies this user, or the pipe is in a state that refuses new clients). Astrid maps this to io::ErrorKind::PermissionDenied instead of endlessly retrying.

Source

Thrown at crates/astrid-core/src/local_transport/windows.rs:201

        .map_err(|_| io::Error::other("named-pipe wait duration overflow"))?
        .max(1);
    // `WaitNamedPipeW` is synchronous, so isolate it from the async worker.
    // Each call is capped at 50 ms: cancelling the outer future stops all
    // retries and leaves at most one short detached blocking wait.
    tokio::task::spawn_blocking(move || {
        let ready = unsafe { WaitNamedPipeW(encoded.as_ptr(), milliseconds) };
        if ready != 0 {
            return Ok(());
        }
        let error = io::Error::last_os_error();
        match error.raw_os_error().map(i32::cast_unsigned) {
            // A bounded timeout is the backoff between open attempts.
            Some(ERROR_SEM_TIMEOUT | ERROR_PIPE_BUSY) => Ok(()),
            Some(ERROR_FILE_NOT_FOUND) => Err(io::Error::new(
                io::ErrorKind::NotFound,
                "Windows named-pipe endpoint disappeared while waiting",
            )),
            Some(ERROR_ACCESS_DENIED) => Err(io::Error::new(
                io::ErrorKind::PermissionDenied,
                "Windows named-pipe endpoint denied access while waiting",
            )),
            _ => Err(error),
        }
    })
    .await
    .map_err(|error| io::Error::other(format!("named-pipe wait task failed: {error}")))?
}

pub(super) async fn connect_outcome(path: &Path) -> io::Result<ConnectOutcome> {
    match connect(path).await {
        Ok(stream) => Ok(ConnectOutcome::Connected(stream)),
        Err(error) if error.kind() == io::ErrorKind::NotFound => Ok(ConnectOutcome::Absent),
        // Named pipes have no stale filesystem node: their namespace object
        // vanishes with the last server handle. Busy and access-denied both
        // prove that something owns the name and must never trigger a daemon
        // boot or unauthenticated fallback.

View on GitHub (pinned to affd8760f4)

Solutions

  1. Run the client under the same user account (or a group allowed by the pipe ACL) as the named-pipe server
  2. Fix the server to create the pipe with permissive security attributes (NULL security descriptor or a DACL granting the client account FILE_GENERIC_READ/WRITE)
  3. Check for name collisions: another process may have created a pipe with the same name; use a unique pipe name

Example fix

// before (server)
CreateNamedPipeW(name, ..., NULL /* default, often restrictive context */);
// after (server)
SECURITY_ATTRIBUTES sa = make_dacl_allowing_clients();
CreateNamedPipeW(name, ..., &sa);
Defensive patterns

Strategy: try-catch

Try / catch

match client.open_with_retry(&pipe).await {
    Err(e) if e.kind() == std::io::ErrorKind::PermissionDenied => {
        return Err(anyhow!("named pipe access denied: run client as an account allowed by the pipe ACL"));
    },
    other => other?,
}

Prevention

When it happens

Trigger: open_client_with_retry -> wait_for_pipe_availability receives GetLastError == ERROR_ACCESS_DENIED while probing the named pipe before a successful connect.

Common situations: The pipe server was created with a restrictive SECURITY_ATTRIBUTES denying the current user; the client runs as a different account/service than the server; another process owns the pipe name with exclusive access.

Understand the failure class

Background: Permission denied / not authorized / 403 Forbidden: access-control rejections when the caller lacks the required role, grant, or ownership — this error's family across 18 libraries.

Related errors


AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09). Data as JSON: /api/errors/aa4c2d343f72387c. Report an issue: GitHub.