denoland/deno · error

helper plist has no CFBundleIdentifier

Error message

helper plist {} has no CFBundleIdentifier

What it means

While bundling a macOS app, deno rewrites each helper's Info.plist CFBundleIdentifier so helper ids nest under the main bundle id (needed for nested-helper launching and code signing). This error means a helper Info.plist parsed successfully but has no CFBundleIdentifier key, or the value is not a string, so there is no 'kind' suffix to preserve.

Solutions

  1. Open the plist named in the error (plutil -p <path>) and confirm CFBundleIdentifier is present and a string.
  2. If it came from the download cache, delete that cache dir and re-run to get a pristine, checksum-verified copy.
  3. If you edited it intentionally, restore a string CFBundleIdentifier (the code derives the new id from the existing value's 'helper' suffix, so any sensible id like com.deno.desktop.helper works).
  4. On a dev checkout (LAUFEY_DEV_DIR), rebuild/re-checkout laufey so the helper plists are regenerated.

Example fix

# before: helper plist lost its id
plutil -p MyApp.app/Contents/Frameworks/laufey.helper.app/Info.plist
# { "CFBundleExecutable": "laufey.helper", ... }   # no CFBundleIdentifier

# after: restore it, or refetch pristine
defaults write <plist> CFBundleIdentifier com.deno.desktop.helper   # or:
rm -rf ~/.cache/deno/desktop/laufey && deno desktop
Defensive patterns

Strategy: validation

Validate before calling

# Validate helper plists before bundling: every one needs a string CFBundleIdentifier
for p in MyApp.app/Contents/Frameworks/*.app/Info.plist; do
  plutil -extract CFBundleIdentifier raw "$p" >/dev/null 2>&1 \
    || { echo "missing CFBundleIdentifier in $p"; exit 1; }
done

Try / catch

Catch the error, name the offending plist path from the message, and fail the bundle step with that path — silently skipping the rewrite would break nested-helper launching and signing downstream, so do not swallow it.

Prevention

When it happens

Trigger: rewrite_helper_plist_identifier() parses a helper plist from the laufey-provided .app where CFBundleIdentifier is missing or of the wrong type — e.g. a hand-edited/corrupted plist in a dev checkout, or a laufey release that changed helper plist shape.

Common situations: A developer edited a helper Info.plist (e.g. experimenting with ids) and dropped the key; a corrupted cache extraction left a truncated plist; upstream laufey renamed the key for some helper.

Related errors


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

Appendix: source

Thrown at cli/tools/desktop.rs:2552

      || format!("failed to rewrite helper plist at {}", plist_path.display()),
    )?;
  }
  Ok(())
}

/// Rewrite a single helper's `CFBundleIdentifier` based on the existing
/// value's "kind" suffix.
fn rewrite_helper_plist_identifier(
  plist_path: &Path,
  main_bundle_id: &str,
) -> Result<(), AnyError> {
  let mut dict: plist::Dictionary = plist::from_file(plist_path)
    .with_context(|| format!("failed to parse {}", plist_path.display()))?;
  let existing = dict
    .get("CFBundleIdentifier")
    .and_then(|v| v.as_string())
    .ok_or_else(|| {
      deno_core::anyhow::anyhow!(
        "helper plist {} has no CFBundleIdentifier",
        plist_path.display()
      )
    })?;
  // Extract the suffix from the last `helper` segment onward. Falls back
  // to a bare `helper` if the existing id doesn't contain that token
  // (defensive — every laufey helper plist has it today).
  let suffix = existing
    .find("helper")
    .map(|i| &existing[i..])
    .unwrap_or("helper");
  let new_id = format!("{main_bundle_id}.{suffix}");
  dict.insert(
    "CFBundleIdentifier".to_string(),
    plist::Value::String(new_id),
  );
  // Write XML format for stability and human-diffability. Helper plists
  // are tiny so we don't gain anything by switching to binary plist

View on GitHub (pinned to f7822238ca)