Fix: SwiftLint "Command Not Found" in Xcode Build Phase

Solve the common SwiftLint build-phase error where Xcode reports the command is not found, especially on Apple Silicon Macs where Homebrew installs to a different path.

The Problem

You added SwiftLint as an Xcode run-script build phase, but the build log shows something like "command not found: swiftlint" or your own "SwiftLint not installed" warning fires — even though you can run `swiftlint` fine in Terminal.

This is one of the most common SwiftLint setup issues, and it is almost never a broken installation. The tool is installed; Xcode's build environment just cannot find it.

It became far more common when Apple Silicon Macs changed where Homebrew installs binaries. Setups that worked for years on Intel Macs suddenly broke on new machines.

The good news: it is quick to fix once you understand why it happens.

Why It Happens

When you run `swiftlint` in Terminal, your shell finds it using the PATH environment variable configured in your shell profile. That profile includes Homebrew's binary directory.

Xcode's build phases do not run inside your interactive shell. They execute in a more limited environment that may not have the same PATH, so the bare `swiftlint` command cannot be located.

On Apple Silicon Macs, Homebrew installs to `/opt/homebrew/bin`, whereas on Intel Macs it used `/usr/local/bin`. A build-phase script that assumes the Intel path — or assumes any particular PATH — fails on the other architecture.

In short, the binary exists, but the script is looking in the wrong place or relying on a PATH that is not present during the build.

Step 1: Confirm Where SwiftLint Is Installed

First, find the actual location of your SwiftLint binary. In Terminal, ask your shell where it is:

`which swiftlint`

The output is the full path. On an Apple Silicon Mac with Homebrew this is typically `/opt/homebrew/bin/swiftlint`, and on Intel it is usually `/usr/local/bin/swiftlint`.

Make note of the exact path you see. This is the value your build phase needs to reference explicitly.

If `which swiftlint` returns nothing, then SwiftLint genuinely is not installed or not on your PATH, and you should install it first before continuing.

Step 2: Reference the Full Path in Your Script

Open your target's Build Phases, expand the SwiftLint run-script phase, and replace a bare `swiftlint` invocation with one that handles both common install locations.

A robust script checks each known path and runs whichever exists:

`if [ -x "/opt/homebrew/bin/swiftlint" ]; then /opt/homebrew/bin/swiftlint; elif [ -x "/usr/local/bin/swiftlint" ]; then /usr/local/bin/swiftlint; else echo "warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint"; fi`

This approach works on both Apple Silicon and Intel machines, which matters when a team shares one project across different hardware.

The `-x` test confirms the file exists and is executable before running it, and the final branch prints a helpful warning rather than failing cryptically.

Step 3: Alternatively, Extend the PATH

If you prefer to keep calling `swiftlint` by name, you can instead add Homebrew's directory to PATH at the top of the script.

Export a PATH that includes both common Homebrew locations, then call `swiftlint` normally. This keeps the invocation clean while still resolving the binary reliably.

Which approach you choose is a matter of taste. Referencing the full path is the most explicit and least surprising, while extending PATH reads a little more naturally.

Either way, the goal is the same: make sure the build environment can locate the binary regardless of which shell profile is or is not loaded.

A Note on Login vs. Non-Interactive Shells

It helps to understand why the environments differ at all. When you open Terminal, you get an interactive login shell that sources your profile files, such as `.zprofile` or `.zshrc`, and those files are where Homebrew adds its directory to PATH.

A build-phase script, by contrast, is executed non-interactively by Xcode. It generally does not source your personal profile, so any PATH additions you made there are simply absent.

This is why advice like "just add Homebrew to your PATH" works in Terminal but does nothing for the build phase. The profile that sets that PATH never runs in the build's context.

Once you internalize that distinction, the fix is obvious: the script must establish what it needs on its own, either by referencing an absolute path or by exporting PATH explicitly at the top. Do not rely on inherited environment you cannot see.

Step 4: Handle Team and CI Consistency

A script that hardcodes one architecture's path will bite a teammate on different hardware. The dual-path check in Step 2 is specifically designed to avoid that.

For larger teams, consider removing the guesswork entirely by pinning SwiftLint as a project dependency, such as a Swift package plugin, so the binary location is managed by the toolchain rather than by each developer's Homebrew setup.

This also aligns local builds with CI, where the environment differs again. A pinned, project-managed SwiftLint runs the same everywhere.

Whatever you decide, commit the build-phase script and any configuration so a fresh checkout just works without every developer re-solving the path problem.

Step 5: Verify the Fix

Save your changes and build with Command-B. Open the build log and find the SwiftLint phase.

You should now see SwiftLint actually running — either clean output or a list of violations — instead of a not-found message. If violations appear in the issue navigator, the fix worked.

If you still see a not-found warning, double-check that the path you hardcoded matches the exact output of `which swiftlint` from Step 1. A typo or a different install location is the usual culprit.

If the script now runs but you hit a permission or sandbox error instead, that is a different, also-common issue related to Xcode's script sandboxing, which has its own dedicated fix.

Prevention and Good Practices

To avoid this class of problem in the future, always reference SwiftLint by a resolved path or an explicitly extended PATH in build phases. Never assume the interactive shell's environment carries over.

When onboarding new machines, especially Apple Silicon Macs, remember that Homebrew's default location changed. Scripts written years ago on Intel hardware may need the dual-path treatment.

Keep a friendly fallback message in the script so that if SwiftLint truly is missing, developers get a clear pointer to install it instead of a cryptic failure.

Finally, remember the boundary: fixing this only restores SwiftLint's ability to run and report style issues. It has no bearing on building, signing, or shipping your app, which remain Xcode's responsibility and require an Apple Developer Program membership for release.

Frequently Asked Questions

Why does swiftlint work in Terminal but not in Xcode?

Terminal uses your shell profile's PATH, which includes Homebrew. Xcode build phases run in a limited environment without that PATH, so a bare swiftlint command cannot be located even though the binary exists.

What is the correct SwiftLint path on Apple Silicon?

Homebrew installs to /opt/homebrew/bin on Apple Silicon Macs, so the binary is typically /opt/homebrew/bin/swiftlint. On Intel Macs it is usually /usr/local/bin/swiftlint. Run which swiftlint to confirm.

Should I hardcode the path or extend PATH in the script?

Both work. Hardcoding the full path is the most explicit and predictable. Extending PATH keeps the swiftlint invocation clean. A dual-path check covers both Apple Silicon and Intel machines.

The script runs now but I get a sandbox error. What next?

That is a separate, common issue caused by Xcode's script sandboxing blocking file access. It has its own fix, typically involving the user script sandboxing build setting.