{"record":{"id":"2676f53e57cf3274","repo":"puppetlabs/puppet","slug":"unable-to-parse-launchd-plist-at-path-job-path","errorCode":null,"errorMessage":"Unable to parse launchd plist at path: #{job_path}","messagePattern":"Unable to parse launchd plist at path: #(.+?)","errorType":"exception","errorClass":"Puppet::Error","httpStatus":null,"severity":"error","filePath":"lib/puppet/provider/service/launchd.rb","lineNumber":242,"sourceCode":"  # Clean out the @property_hash variable containing the cached list of services\n  def flush\n    @property_hash.clear\n  end\n\n  def exists?\n    Puppet.debug(\"Puppet::Provider::Launchd:Ensure for #{@property_hash[:name]}: #{@property_hash[:ensure]}\")\n    @property_hash[:ensure] != :absent\n  end\n\n  # finds the path for a given label and returns the path and parsed plist\n  # as an array of [path, plist]. Note plist is really a Hash here.\n  def plist_from_label(label)\n    job = self.class.jobsearch(label)\n    job_path = job[label]\n    if FileTest.file?(job_path)\n      job_plist = self.class.read_plist(job_path)\n    else\n      raise Puppet::Error, \"Unable to parse launchd plist at path: #{job_path}\"\n    end\n    [job_path, job_plist]\n  end\n\n  # when a service includes hasstatus=>false, we override the launchctl\n  # status mechanism and fall back to the base provider status method.\n  def status\n    if @resource && ((@resource[:hasstatus] == :false) || (@resource[:status]))\n      super\n    elsif @property_hash[:status].nil?\n      # property_hash was flushed so the service changed status\n      service_name = @resource[:name]\n      # Updating services with new statuses\n      job_list = self.class.job_list\n      # if job is present in job_list, return its status\n      if job_list.key?(service_name)\n        job_list[service_name]\n      # if job is no longer present in job_list, it was stopped","sourceCodeStart":224,"sourceCodeEnd":260,"githubUrl":"https://github.com/puppetlabs/puppet/blob/e227c27540975c25aa22d533a52424a9d2fc886a/lib/puppet/provider/service/launchd.rb#L224-L260","documentation":"After jobsearch resolves a label to a plist path, plist_from_label guards with FileTest.file?(job_path) and raises when the path is not a regular file. This catches a stale label-to-path mapping: the map said the plist lives there, but on disk the file was deleted, replaced by a directory, or is a dangling symlink between the map build and the read.","triggerScenarios":"The label map was built (or refreshed once) and the plist was removed afterwards — e.g. a package resource earlier in the same run uninstalled the daemon; or jobsearch returned a path where a non-file now sits. The raise happens before self.class.read_plist is attempted.","commonSituations":"Uninstall/reinstall flows where a service resource still references a job whose plist a package just removed; broken symlinks in /Library/LaunchDaemons left by hand-rolled uninstall scripts; concurrent management tools deleting plists mid-run.","solutions":["Check the path on disk: test -f on the job_path shown/derivable from the error","Remove or repair dangling entries, then re-run so make_label_to_path_map rebuilds without them","Order resources so a managed service is absent/disabled before the package removes its plist","Ensure only one tool owns LaunchDaemons to avoid mid-run races"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"# Ruby, before relying on a resolved job path\nraise 'job plist missing' unless FileTest.file?(job_path)","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Make uninstall flows declare service ensure => absent before the package removes the plist","Avoid dangling LaunchDaemons symlinks; clean them in the same run that removes the target","Give launchd management a single owner (Puppet or the vendor tool) to prevent mid-run plist races"],"tags":["puppet","launchd","macos","plist","stale-path"],"backgroundTag":"file-not-found","analyzedSha":"e227c27540975c25aa22d533a52424a9d2fc886a","analyzedAt":"2026-08-21T20:49:46.650Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}