Skip to content

Removing a target referenced by PBXFileSystemSynchronizedBuildFileExceptionSet crashes serialization #1093

@Alexis-Fab

Description

@Alexis-Fab

Summary

Calling removeFromProject() on a PBXNativeTarget does not cascade to PBXFileSystemSynchronizedBuildFileExceptionSet objects that reference that target. The orphaned exception sets retain a nil target reference, which crashes during serialization when generating the plist annotation (target.name on nil).

Minimal reproduction

Setup (Xcode 16+):

  1. Create a project with two app targets: AppA and AppB
  2. Create a folder SharedSrc/ containing Shared.swift and Info.plist
  3. In Xcode, drag SharedSrc/ into the project as a synchronized folder reference, with both targets checked
  4. Select Info.plist in the navigator, open the File Inspector, and uncheck AppB from Target Membership

This creates a PBXFileSystemSynchronizedBuildFileExceptionSet with target = AppB inside the SharedSrc synchronized root group — meaning "exclude Info.plist from AppB's build."

Reproduction (Ruby xcodeproj gem v1.27.0):

require "xcodeproj"

proj = Xcodeproj::Project.open("Repro.xcodeproj")

# Removing AppA works fine (not referenced by any exception set)
proj.targets.find { |t| t.name == "AppA" }.remove_from_project
proj.save  # => OK

# Removing AppB crashes (referenced by the exception set)
proj.targets.find { |t| t.name == "AppB" }.remove_from_project
proj.save  # => CRASH

Crash:

PBXFileSystemSynchronizedBuildFileExceptionSet#display_name: undefined method 'name' for nil (NoMethodError)
  "Exceptions for \"#{GroupableHelper.parent(self).display_name}\" in \"#{target.name}\" target"

What happens

  • removeFromProject() on the target removes the PBXNativeTarget object, its build configuration list, and its build phases
  • It does not remove PBXFileSystemSynchronizedBuildFileExceptionSet objects whose target property pointed to the removed target
  • On save, the serializer calls display_name on each exception set, which accesses target.name — now nil — and crashes

What should happen

removeFromProject() on a PBXNativeTarget should also remove any PBXFileSystemSynchronizedBuildFileExceptionSet whose target references the removed target. Xcode's UI does this cleanup when deleting a target — the programmatic API should match.

Additional incomplete cascading

Beyond exception sets, removeFromProject() also leaves behind:

  • PBXFileReference for the target's product (.app, .xctest)
  • The product reference in the Products group
  • PBXGroup entries for the target's source directory

These don't crash serialization but leave orphaned entries in the project file.

Workaround

Clean up orphaned exception sets manually before saving:

proj.objects.select { |o|
  o.isa == "PBXFileSystemSynchronizedBuildFileExceptionSet" &&
  o.respond_to?(:target) && o.target.nil?
}.each { |o| o.remove_from_project }

Environment

  • Ruby xcodeproj gem 1.27.0
  • Project created with Xcode 26 beta (uses synchronized folders / PBXFileSystemSynchronizedRootGroup)
  • Also reproduced via xcodeproj-mcp-server v1.5.0, which uses the Swift tuist/XcodeProj library — same crash (exit code 133 / SIGTRAP, the Swift equivalent of nil access)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions