denoland/deno · error

bundle identifier is empty

Error message

bundle identifier is empty

What it means

Thrown by validate_bundle_identifier when the identifier passed via `--identifier` is the empty string. The identifier becomes Apple's CFBundleIdentifier, the Linux .desktop filename, and the Windows AppUserModelID, so it must be a non-empty reverse-DNS string. An empty value usually comes from shell quoting eating the argument rather than from intent.

Source

Thrown at cli/tools/desktop.rs:2463

/// or silently mis-extract. Returns `None` on any read or parse failure.
fn read_plist_string(path: &Path, key: &str) -> Option<String> {
  let dict: plist::Dictionary = plist::from_file(path).ok()?;
  dict.get(key)?.as_string().map(|s| s.to_string())
}

/// Validate a reverse-DNS bundle identifier (Apple `CFBundleIdentifier`,
/// also used for Linux `.desktop` filenames and Windows AppUserModelID).
///
/// Apple's rules: ASCII alphanumerics, hyphens, and dots; must have at
/// least one dot (so it looks like reverse DNS); each dot-separated
/// segment must be non-empty and not start with a digit. We don't
/// enforce the segment-leading-letter rule strictly (some legacy apps
/// use digits) but we do reject empty segments and obvious shell
/// metacharacters — the identifier ends up as a `codesign` argument and
/// a path component of the helper bundles.
fn validate_bundle_identifier(id: &str) -> Result<(), AnyError> {
  if id.is_empty() {
    bail!("bundle identifier is empty");
  }
  if id.len() > 155 {
    // Apple's documented limit for CFBundleIdentifier on receipts is
    // 155 chars; bigger values quietly truncate elsewhere in the
    // toolchain.
    bail!("bundle identifier {id:?} is longer than 155 characters");
  }
  if !id.contains('.') {
    bail!(
      "bundle identifier {id:?} must be in reverse-DNS form (e.g. com.acme.foo)"
    );
  }
  for c in id.chars() {
    if !(c.is_ascii_alphanumeric() || c == '.' || c == '-') {
      bail!(
        "bundle identifier {id:?} must match [A-Za-z0-9.-]+, but contains {c:?}",
      );
    }

View on GitHub (pinned to f7822238ca)

Solutions

  1. Provide a real reverse-DNS identifier: `--identifier com.acme.myapp`.
  2. In CI, verify the variable is set first: `test -n "$BUNDLE_ID" || exit 1`.
  3. Or omit `--identifier` to let Deno synthesize `com.deno.desktop.<app-name-slug>`.

Example fix

# before
deno desktop --identifier "$BUNDLE_ID" main.ts   # BUNDLE_ID unset -> ""

# after
export BUNDLE_ID=com.acme.myapp
deno desktop --identifier "$BUNDLE_ID" main.ts
Defensive patterns

Strategy: validation

Validate before calling

# bash: refuse to pass an empty identifier
[[ -n "${BUNDLE_ID:-}" ]] || { echo "BUNDLE_ID is empty" >&2; exit 1; }
deno desktop --identifier "$BUNDLE_ID" main.ts

Type guard

// TypeScript
function isNonEmptyBundleIdentifier(id: string | undefined): id is string {
  return typeof id === "string" && id.length > 0;
}

Prevention

When it happens

Trigger: `deno desktop --identifier "" main.ts`; `--identifier $BUNDLE_ID` with the variable unset in the shell/CI; a config/CI pipeline templating an empty value into the flag.

Common situations: CI where the identifier secret/var is not exported for the job; a shell script with a typo'd variable name expanding to empty; copy-pasted command where the value was deleted but quotes remained.

Related errors


AI-assisted analysis of denoland/deno@f7822238ca (2026-08-20). Data as JSON: /api/errors/c5fa5086568edff9. Report an issue: GitHub.