Hmbown/CodeWhale · error · anyhow::Error

refusing non-loopback app-server bind without explicit auth

Error message

refusing non-loopback app-server bind without explicit auth token; pass --auth-token or set CODEWHALE_APP_SERVER_TOKEN

What it means

open_bundle_directory opens a plugin bundle directory on Windows with BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT and deny-write sharing, then requires the handle to be a real directory without the reparse attribute. A junction or directory-symlink, or a path that actually names a file, is rejected with InvalidData.

Source

Thrown at crates/app-server/src/lib.rs:701

fn resolve_auth_token(options: &AppServerOptions) -> Result<Option<String>> {
    let configured = options.auth_token.as_ref().map(|token| token.trim());
    if let Some(token) = configured
        && token.is_empty()
    {
        bail!("app-server auth token cannot be empty");
    }
    let has_explicit_token = configured.is_some();

    if options.insecure_no_auth {
        if !options.listen.ip().is_loopback() {
            bail!("refusing unauthenticated app-server bind on non-loopback address");
        }
        eprintln!("warning: app-server HTTP auth disabled by --insecure-no-auth");
        return Ok(None);
    }

    if !has_explicit_token && !options.listen.ip().is_loopback() {
        bail!(
            "refusing non-loopback app-server bind without explicit auth token; pass --auth-token or set CODEWHALE_APP_SERVER_TOKEN"
        );
    }

    let token = configured
        .map(str::to_string)
        .unwrap_or_else(|| format!("cwapp_{}", Uuid::new_v4().simple()));
    for line in app_server_auth_status_lines(has_explicit_token) {
        eprintln!("{line}");
    }
    Ok(Some(token))
}

fn app_server_auth_status_lines(has_explicit_token: bool) -> Vec<&'static str> {
    if has_explicit_token {
        return vec!["app-server auth: bearer token required for HTTP routes."];
    }
    vec![

View on GitHub (pinned to 0c42157ee5)

Solutions

  1. Copy the plugin tree to a real directory instead of junctioning it, and point Codewhale there
  2. Reinstall the plugin so Codewhale stages its own directory
  3. Remove the junction ('rmdir <link>' removes only the link on Windows) and restore a physical directory
Defensive patterns

Strategy: validation

Validate before calling

const FILE_ATTRIBUTE_REPARSE_POINT: u32 = 0x400;

fn is_real_directory(path: &std::path::Path) -> bool {
    match std::fs::symlink_metadata(path) {
        Ok(md) => md.is_dir() && md.file_attributes() & FILE_ATTRIBUTE_REPARSE_POINT == 0,
        Err(_) => false,
    }
}

Type guard

fn is_physical_dir(p: &std::path::Path) -> bool {
    std::fs::symlink_metadata(p).map(|m| m.is_dir() && m.file_attributes() & 0x400 == 0).unwrap_or(false)
}

Try / catch

match open_bundle_directory(&path) {
    Err(e) if e.to_string().contains("reparse point or non-directory") => {
        // replace the junction with a copied, real directory and retry
    }
    other => other?,
}

Prevention

When it happens

Trigger: Reviewing a plugin whose directory is a junction or directory symlink (for example a shared plugins dir linked into multiple installs), or passing a file path where a bundle directory is expected.

Common situations: Portable installs sharing one plugins directory via junction; backup restores that materialize directories as links; dedupe tools junctioning large folders.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@0c42157ee5 (2026-08-20). Data as JSON: /api/errors/d93e9ececf7513b6. Report an issue: GitHub.