Skip to content

Git says "You have not agreed to the Xcode license agreements"

Posted in Git, Macos, Cli

By Dušan Dželebdžić

Photo by Scott Graham on Unsplash
Photo by Scott Graham on Unsplash

I was fixing a mobile toolbar in WebKeeper when git status decided we needed to talk about Xcode.

You have not agreed to the Xcode license agreements. Please run 'sudo xcodebuild -license' from within a Terminal window to review and agree to the Xcode and Apple SDKs license.

git log said the same thing. I was changing CSS. Apparently the toolbar had picked up an Apple SDK dependency overnight.

The timing is suspicious. Xcode 27 shipped on September 14, and this was September 15. When I checked afterwards, the Xcode app on the machine reported version 27.0. I can't prove the new Xcode is what triggered the prompt, because I never caught the moment it changed. But if you're reading this the week of an Xcode release, you know where I'd look first.

Why I didn't just accept it

The error message tells you the fix, and every other page on the internet repeats it: sudo xcodebuild -license accept.

I didn't want to do that in the middle of a release. It's sudo, and it's a legal agreement, and I was trying to ship a CSS fix. That felt like a lot of ceremony for git status, especially since I had a hunch there was another Git on the machine that didn't care about Xcode at all.

There was already another Git on the machine

The first Git on my path was /usr/bin/git. But both of these existed too:

/Applications/Xcode.app/Contents/Developer/usr/bin/git
/Library/Developer/CommandLineTools/usr/bin/git

The second one is the interesting one.

/usr/bin/git isn't really Git. It's a small wrapper that asks macOS which developer installation is selected and runs the Git from there. The standalone Command Line Tools package lives in its own directory, completely separate from the Xcode app. Apple explains both in its command-line tools FAQ.

So which one was selected?

xcode-select -p
/Applications/Xcode.app/Contents/Developer

Full Xcode. The one with the unsigned license. Seeing /usr/bin/git first on the path told me nothing about which Git would actually run.

One environment variable

I pointed a single command at the Command Line Tools instead:

DEVELOPER_DIR=/Library/Developer/CommandLineTools git status --short

It worked. So did git log.

DEVELOPER_DIR overrides the selected developer directory for the command that receives it. Apple documents it in Configuring command-line tools settings. The machine's default selection stays exactly as it was, and nothing gets agreed to.

If you want to try it, check that the tools are actually there first:

ls -l /Library/Developer/CommandLineTools/usr/bin/git
DEVELOPER_DIR=/Library/Developer/CommandLineTools git --version

On my machine:

git version 2.50.1 (Apple Git-155)

You can also ask xcrun which binary it would pick:

DEVELOPER_DIR=/Library/Developer/CommandLineTools xcrun --find git
/Library/Developer/CommandLineTools/usr/bin/git

This only works if the Command Line Tools are installed. An environment variable won't conjure them out of nothing. If the directory is empty, you're back to accepting the license.

Then the GitHub CLI tripped over the same thing

Later in the release, GitHub CLI failed with a longer version of the same message:

failed to determine base repo: failed to run git: You have not agreed to the Xcode license agreements. Please run 'sudo xcodebuild -license' from within a Terminal window to review and agree to the Xcode and Apple SDKs license.

gh was trying to figure out which repository I was in, so it ran Git. Git hit the license wall, and gh passed the complaint along.

The catch was that I'd only given the override to the previous command. A prefix assignment applies to that one command and nothing after it:

# Only this command gets the override.
DEVELOPER_DIR=/Library/Developer/CommandLineTools git status

# This one runs without it.
gh run list

For a run of release commands, export it instead:

export DEVELOPER_DIR=/Library/Developer/CommandLineTools

git status
gh run list

Now everything started from that shell gets it, including Git when another tool calls it. When you're done:

unset DEVELOPER_DIR

For read-only CI checks, gh run view --repo owner/repository also works, because it skips the local repository lookup entirely.

Takeaway

The release went out with commits, a push, and green CI, all on a Git that had been sitting on disk the whole time. A while later the plain git worked again without the override. I still don't know what changed in between.

When Git suddenly wants you to sign something, check which developer directory it's using before you reach for sudo.

Found this useful? Pass it on.

Follow me on X or LinkedIn for the next one.