denoland/deno · error

workspace root is not a local directory

Error message

workspace root is not a local directory

What it means

The native type checker derives a project root path from the workspace's root_dir_url via Url::to_file_path (cli/tools/check.rs:68-74); the conversion fails when the root is not a file:// URL. Native check materializes tsconfigs and syncs types under that directory, so a non-local workspace root cannot be checked.

Source

Thrown at cli/tools/check.rs:73

  check_flags: CheckFlags,
) -> Result<(), AnyError> {
  if check_flags.doc || check_flags.doc_only {
    // Doc snippet extraction was handled by Deno 2.x's forked tsc; the native
    // compiler does not type-check markdown/JSDoc snippets yet.
    log::warn!(
      "{} --doc/--doc-only is not yet supported by the native type checker and will be ignored",
      colors::yellow("Warning")
    );
  }

  let factory = CliFactory::from_flags(flags.clone());
  let cli_options = factory.cli_options()?;
  let project_root = cli_options
    .workspace()
    .root_dir_url()
    .to_file_path()
    .map_err(|_| {
      deno_core::anyhow::anyhow!("workspace root is not a local directory")
    })?;

  // Build the module graph over the requested roots (or the whole project).
  // Deno owns resolution: this drives deno's own graph diagnostics (missing
  // modules + hints) and the incremental type-check cache, so we can skip the
  // external compiler entirely when nothing it sees has changed.
  // Resolve the requested roots the same way `deno check` always has:
  // globs are expanded, workspace `exclude` is applied, and
  // `include_ignored_specified: false` means an explicitly-passed excluded file
  // is skipped rather than force-checked. An empty result is not an error - it
  // just means there's nothing to check (e.g. every match was excluded), which
  // deno reports as a warning and a clean exit.
  let graph_container = factory.main_module_graph_container().await?;
  let mut roots = graph_container.collect_specifiers(
    &check_flags.files,
    crate::graph_container::CollectSpecifiersOptions {
      include_ignored_specified: false,
    },

View on GitHub (pinned to 89f33cbef2)

Solutions

  1. Run deno check from a local directory with a local deno.json/workspace (the root must map to a real directory)
  2. Clone/download the remote-configured project locally and check it there
  3. If you believe the root is local yet this fires, check for cwd weirdness (deleted directory, exotic mount) and re-run from a stable path

Example fix

# before: workspace rooted at a remote config
deno check --config https://example.com/deno.json src/
# after: local config
Deno.writeTextFileSync('deno.json', await (await fetch('https://example.com/deno.json')).text());
deno check --config deno.json src/
Defensive patterns

Strategy: type-guard

Validate before calling

// Verify the workspace root is a local directory before native check
function assertLocalWorkspaceRoot(rootUrl: string) {
  if (new URL(rootUrl).protocol !== 'file:') {
    throw new Error(`native check requires a local workspace root: ${rootUrl}`);
  }
}

Type guard

function isLocalFileUrl(url: string): url is `file://${string}` {
  try { return new URL(url).protocol === 'file:'; } catch { return false; }
}

Prevention

When it happens

Trigger: A workspace whose root resolves to a non-file scheme - e.g. a config/workspace rooted at a remote URL rather than a local directory; unusual embedded/virtual filesystem environments where the root URL lacks a local path.

Common situations: Experiments driving deno check against remote-configured workspaces; harnesses running Deno in sandboxes whose cwd/root is not representable as a local path.

Related errors


AI-assisted analysis of denoland/deno@89f33cbef2 (2026-08-16). Data as JSON: /api/errors/753b5fba7be78eb0. Report an issue: GitHub.