{"record":{"id":"83151c9498f4b432","repo":"denoland/deno","slug":"bundle-identifier-id-has-an-empty-segment","errorCode":null,"errorMessage":"bundle identifier {id:?} has an empty segment","messagePattern":"bundle identifier (.+?) has an empty segment","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cli/tools/desktop.rs","lineNumber":2484,"sourceCode":"    // Apple's documented limit for CFBundleIdentifier on receipts is\n    // 155 chars; bigger values quietly truncate elsewhere in the\n    // toolchain.\n    bail!(\"bundle identifier {id:?} is longer than 155 characters\");\n  }\n  if !id.contains('.') {\n    bail!(\n      \"bundle identifier {id:?} must be in reverse-DNS form (e.g. com.acme.foo)\"\n    );\n  }\n  for c in id.chars() {\n    if !(c.is_ascii_alphanumeric() || c == '.' || c == '-') {\n      bail!(\n        \"bundle identifier {id:?} must match [A-Za-z0-9.-]+, but contains {c:?}\",\n      );\n    }\n  }\n  if id.split('.').any(|seg| seg.is_empty()) {\n    bail!(\"bundle identifier {id:?} has an empty segment\");\n  }\n  Ok(())\n}\n\n/// Walk every `.app` under `Contents/Frameworks/` and rewrite its\n/// `CFBundleIdentifier` so it's a strict suffix of `main_bundle_id`.\n///\n/// CEF's process model: when the browser process spawns a helper for a\n/// child role (gpu, renderer, plugin, …), the helper inspects its own\n/// `CFBundleIdentifier` and refuses to attach to a parent whose id\n/// doesn't match it as a prefix. laufey's default helper plists ship with\n/// `com.example.laufey.helper.*` — which is inconsistent with whatever id\n/// we wrote into the main bundle, so we'd get a launch-time refusal\n/// (the helper exits silently and the browser hangs waiting for it).\n///\n/// We compute the new id by extracting the \"kind\" suffix from the\n/// existing id (everything from the last `helper` segment onward) and\n/// concatenating it onto the main id. So `com.example.laufey.helper` →","sourceCodeStart":2466,"sourceCodeEnd":2502,"githubUrl":"https://github.com/denoland/deno/blob/f7822238cab635a3a19f99f493f675fa81a7f9d8/cli/tools/desktop.rs#L2466-L2502","documentation":"Thrown by validate_bundle_identifier in deno desktop's macOS bundling path. After checking the identifier's overall charset, the validator splits it on '.' and rejects any identifier containing an empty segment (leading dot, trailing dot, or consecutive dots). Empty segments produce a malformed CFBundleIdentifier that macOS codesigning, Launch Services, and CEF helper-process matching all mishandle.","triggerScenarios":"Running `deno desktop` with a configured bundle identifier (via the desktop identifier flag/config) whose dotted form has an empty segment: '.com.acme.app', 'com.acme.', or 'com..acme'. The charset check ([A-Za-z0-9.-]) passes for these, so this segment check is what catches them.","commonSituations":"Hand-typed identifiers with a typo, identifiers assembled by joining variables where one variable is an empty string, or a trailing dot copy-pasted from a sentence like 'our id is com.acme.foo.'.","solutions":["Edit the identifier to have non-empty dot-separated segments, e.g. 'com.acme.app' — no leading, trailing, or double dots.","If the id is assembled from parts (org, product, suffix), assert each part is non-empty before joining with '.'.","Keep segments to ASCII alphanumerics and '-' so the earlier charset check also passes."],"exampleFix":"// deno.json (desktop config) — before\n{\n  \"desktop\": { \"identifier\": \"com.acme.\" } }\n\n// after\n{\n  \"desktop\": { \"identifier\": \"com.acme.app\" } }","handlingStrategy":"validation","validationCode":"// Run before `deno desktop` (e.g. in a prebuild script)\nfunction assertBundleId(id: string): void {\n  if (!/^[A-Za-z0-9.-]+$/.test(id)) throw new Error(`bad charset: ${id}`);\n  if (id.split(\".\").some((seg) => seg.length === 0)) {\n    throw new Error(`bundle identifier '${id}' has an empty segment`);\n  }\n}\nassertBundleId(config.desktop.identifier);","typeGuard":"function isValidBundleId(id: string): boolean {\n  return /^[A-Za-z0-9.-]+$/.test(id) && !id.split(\".\").some((s) => s === \"\");\n}","tryCatchPattern":null,"preventionTips":["Derive the bundle id from fixed constants, never from string-joining unchecked variables.","Add a config lint step in CI that runs the segment check before any desktop build.","Include one golden identifier (com.example.app) in the project README next to the config key."],"tags":["desktop","macos","bundle-identifier","config-validation"],"backgroundTag":"bundle-identifier-validation","analyzedSha":"f7822238cab635a3a19f99f493f675fa81a7f9d8","analyzedAt":"2026-08-20T13:07:44.778Z","contentChangedAt":"2026-08-20T13:07:44.778Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}