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
- Open the plist named in the error (plutil -p <path>) and confirm CFBundleIdentifier is present and a string.
- If it came from the download cache, delete that cache dir and re-run to get a pristine, checksum-verified copy.
- 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).
- 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
- Never edit helper Info.plists without keeping CFBundleIdentifier a string.
- Regenerate hand-modified .app bundles from source instead of patching them in place.
- Keep the laufey cache pristine; if plists look odd, clear the cache and re-verify via checksum.
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
- CFBundleIdentifier is empty in
- could not find ' ' .app bundle inside (backend may not ship…
- could not find laufey.app under
- plutil could not read CFBundleIdentifier from
- app bundle has no name
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 plistView on GitHub (pinned to f7822238ca)