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
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions