gleam-lang/gleam · critical

Unable to start Tokio async runtime

Error message

Unable to start Tokio async runtime

What it means

`get_manifest_details` in compiler-cli/src/dependencies.rs:99 builds a Tokio multi-threaded runtime so it can query the Hex API when resolving dependency metadata for `gleam deps tree`/`gleam deps list`. `tokio::runtime::Runtime::new()` fails when the OS refuses resources the runtime needs — worker threads (`clone` under thread/process limits) or the epoll/timerfd descriptors for the IO and time drivers — and `.expect("Unable to start Tokio async runtime")` converts that into a process abort.

Source

Thrown at compiler-cli/src/dependencies.rs:99

        build_tools: vec![],
        name: config.name.clone(),
        requirements: config.all_direct_dependencies()?.keys().cloned().collect(),
        version: config.version.clone(),
        source: ManifestPackageSource::Local {
            path: paths.root().to_path_buf(),
        },
        otp_app: None,
    };

    // Get the manifest packages and add the root package to the vec
    let mut packages = manifest.packages.iter().cloned().collect_vec();
    packages.push(root_package);

    list_package_and_dependencies_tree(std::io::stdout(), options, packages.clone(), config.name)
}

fn get_manifest_details(paths: &ProjectPaths) -> Result<(PackageConfig, Manifest)> {
    let runtime = tokio::runtime::Runtime::new().expect("Unable to start Tokio async runtime");
    let config = crate::config::root_config(paths)?;
    let package_fetcher = PackageFetcher::new(runtime.handle().clone());
    let dependency_manager = DependencyManagerConfig {
        use_manifest: UseManifest::Yes,
        check_major_versions: CheckMajorVersions::No,
    }
    .into_dependency_manager(
        runtime.handle().clone(),
        package_fetcher,
        cli::Reporter::new(),
        Mode::Dev,
    );
    let manifest = dependency_manager
        .resolve_versions(paths, &config, Vec::new())?
        .manifest;
    Ok((config, manifest))
}

View on GitHub (pinned to 7e623aa83d)

Solutions

  1. Check and raise thread/process limits: `ulimit -u` on the host, `--pids-limit` for Docker, `podPidsLimit`/kubelet config in Kubernetes
  2. Check and raise fd limits: `ulimit -n 4096`; find fd leaks with `ls /proc/$$/fd | wc -l`
  3. Free memory or raise the container memory limit so Tokio worker thread stacks are allocatable
  4. If sandboxed, permit `clone`, `epoll_create1`, `eventfd`, and `timerfd_*` syscalls or run outside that sandbox
  5. Re-run `gleam deps tree` once limits are fixed — no project change is required

Example fix

# before: panics 'Unable to start Tokio async runtime'
docker run --pids-limit 10 my-ci gleam deps tree

# after: leave room for Tokio worker threads
docker run --pids-limit 512 my-ci gleam deps tree
Defensive patterns

Strategy: validation

Validate before calling

# fail fast if the box cannot host a Tokio runtime
nproc_lim=$(ulimit -u); nofile=$(ulimit -n)
[ "$nproc_lim" != "unlimited" ] && [ "$nproc_lim" -lt 256 ] && { echo "nproc limit too low: $nproc_lim"; exit 1; }
[ "$nofile" != "unlimited" ] && [ "$nofile" -lt 256 ] && { echo "nofile limit too low: $nofile"; exit 1; }
gleam deps tree

Prevention

When it happens

Trigger: Running `gleam deps tree` or `gleam deps list` where `Runtime::new()` fails: `ulimit -u`/RLIMIT_NPROC or a cgroup `pids.max` limit blocking worker-thread creation, `ulimit -n`/RLIMIT_NOFILE exhaustion (EMFILE) when creating the epoll/timer fds, or a seccomp/AppArmor profile blocking `clone`/`epoll_create1`.

Common situations: Minimal Docker images or Kubernetes pods with `--pids-limit`/`podPidsLimit` set low; build agents with thousands of leaked fds; hardened runtimes (gVisor, custom seccomp) blocking syscalls Tokio needs; memory-exhausted machines that cannot allocate thread stacks.

Related errors


AI-assisted analysis of gleam-lang/gleam@7e623aa83d (2026-08-17). Data as JSON: /api/errors/7027e02cc2d56d9b. Report an issue: GitHub.