Monthly Archives: May 2026

SWTBot Tests Failing on GitHub Actions macOS Because of “Welcome to Mac” (Setup Assistant Analytics)

My SWTBot UI tests recently started failing on GitHub Actions macOS runners.

The application under test is an Eclipse-based application, so at first I expected the failure to be caused by some SWTBot timing issue, focus issue, or maybe a regression in my own code.

However, the screenshot captured by the failed test showed something completely unrelated to my application:

That is not Eclipse. That is macOS Setup Assistant.

More specifically, it was the macOS Analytics screen asking whether to share analytics data with Apple and app developers. Since SWTBot drives the UI that currently has focus, this dialog was stealing focus from Eclipse and making the test interact with the wrong window.

The first workaround

My first attempt was to dismiss the Analytics dialog before starting the SWTBot tests:

This looked reasonable, but then the failure changed.

The new screenshot showed another Setup Assistant page:

This time it was the “Welcome to Mac” screen.

So the script was managing to click something, but Setup Assistant was still not completely gone. SWTBot was still losing focus before the actual Eclipse application could be tested.

The final workaround

The most robust fix was to avoid heredocs entirely and pass the AppleScript using repeated osascript -e arguments.

Here is the workflow step I now use:

This does three things:

  1. It checks whether Setup Assistant is running.
  2. It tries to press the visible Continue button.
  3. If Setup Assistant still refuses to disappear, it kills the process before the UI tests start.

After adding this step before launching Eclipse, the SWTBot tests started passing again.

Why this matters for SWTBot

SWTBot tests are particularly sensitive to focus problems.

They do not just test some isolated backend logic. They drive a real UI. If another native macOS window appears in front of the Eclipse workbench, the test can fail in confusing ways:

The actual problem may have nothing to do with SWTBot or Eclipse. The test may simply be interacting with the wrong application.

That is why screenshots from failed UI tests are invaluable. Without the screenshot, I would probably have spent a lot more time debugging Eclipse, SWTBot, or my own application.

Running Homebrew Commands with sudo on Linux and macOS

Sometimes a command installed with Homebrew needs to be executed as root. A good example is nethogs, which needs elevated privileges to inspect network traffic.

The problem is that this often fails:

even though this works:

The reason is that sudo usually runs with a different, restricted PATH. Your normal shell can find Homebrew commands, but sudo may not know where Homebrew installed them.

On Linux, Homebrew commands may live somewhere like:

On macOS, they are commonly under:

on Apple Silicon, or:

on Intel Macs.

The simple one-command solution

A quick workaround is to resolve the command path before calling sudo:

This works because which nethogs is evaluated by your normal shell, using your normal PATH, before sudo runs.

So instead of asking sudo to find nethogs, you give it the full path directly.

A shell function for one command

You can wrap that in a function:

This lets you run:

and still preserve arguments correctly.

A general brew-sudo helper

A better cross-platform approach is to define a small helper that asks Homebrew for its prefix, checks both bin and sbin, and then runs the matching executable with sudo.

Now you can run Homebrew-installed commands with root privileges like this:

This works across Linux and macOS because brew --prefix returns the correct Homebrew prefix for the current system.

Defining command-specific wrappers

You can then define convenient wrappers in terms of brew-sudo:

This gives you the best of both worlds:

while internally resolving to something like:

on Linux, or the corresponding Homebrew path on macOS.

Full version

Put this in a shell file sourced by both bash and zsh, or copy it into both ~/.bashrc and ~/.zshrc:

A note on sudo and Homebrew

This approach does NOT run brew itself with sudo. It only uses brew --prefix to locate Homebrew’s installation directory, then runs a specific Homebrew-installed executable with sudo.

That distinction matters: running brew install or brew upgrade with sudo is generally the wrong approach, but running a tool like nethogs with sudo is appropriate when the tool itself needs root privileges.

Alternative: adding Homebrew to /etc/sudoers

Another possible solution is to teach sudo about Homebrew’s executable directories.

On many systems, sudo uses a restricted path configured in /etc/sudoers, often through a setting called secure_path. You can inspect and edit it with:

You may see a line like this:

One could add Homebrew’s bin and sbin directories there. For example, on Linuxbrew:

Or on Apple Silicon macOS:

After that, this may work directly:

However, this approach has a security tradeoff.

It does not give users new sudo permissions by itself. A user still needs to be allowed to run commands with sudo in the first place. But it does make Homebrew-installed commands part of sudo’s command lookup path.

That can be undesirable because Homebrew prefixes are often writable by the normal user, or by users in an admin group. In general, root’s PATH should avoid directories that non-root users can modify. Otherwise, it becomes easier to run a user-controlled executable as root accidentally.

For that reason, I prefer the wrapper approach shown above.

Bootstrapping chezmoi from HTTPS to SSH After First Apply

When I bootstrap a new machine with chezmoi, I usually start by cloning my dotfiles repo from GitHub over HTTPS:

That works well for the very first setup, because my SSH keys are not installed yet.

After the first chezmoi apply, though, my SSH keys are finally in place, and I want the dotfiles repository itself to switch from HTTPS to SSH automatically.

This sounds simple, but there is a small gotcha.

The goal

I wanted chezmoi apply to do this at the end of the first run:

  • detect that the repo remote is using HTTPS
  • convert it to the SSH form
  • update origin automatically

For example:

  • from https://github.com/username/dotfiles.git
  • to git@github.com:username/dotfiles.git

My first attempt

My first version used a run_once_after_... script that called chezmoi source-path to find the source repo:

But when I ran chezmoi apply, I got this:

Why this fails

The problem is that the script is being run by chezmoi apply.

So if the script itself runs another chezmoi command, the second process tries to acquire the same persistent state lock that the original chezmoi apply already holds.

In other words:

  • chezmoi apply starts
  • it runs your script
  • your script runs chezmoi source-path
  • the nested chezmoi process waits on the lock
  • eventually it times out

The fix

The solution is to avoid calling chezmoi inside the script.

Instead, make the script a template and use {{ .chezmoi.sourceDir }} to inject the source directory path when chezmoi renders the script.

Here is the working version.

Working script

Create this file:

Contents:

Why this works

There are two important details here.

1. The script is a template

The .tmpl suffix tells chezmoi to render the file before executing it.

That means this line:

gets replaced with the actual source directory path ahead of time.

No extra chezmoi process is needed.

2. The script runs after “apply”, and only once

Because the file name starts with run_once_after_, chezmoi runs it after the apply phase, and only once per machine unless the script changes.

That makes it a good fit for first-boot setup tasks like this one.

A note about the working directory

One subtle point: chezmoi scripts do not run in the source repo by default.

So even though we know the source directory, we still need to explicitly run:

before calling git remote get-url or git remote set-url.

Resetting the script state

If you need to test the script again after it has already run successfully, you can clear chezmoi’s script state:

That lets run_once_ scripts execute again.

Final thoughts

This ended up being a neat example of a general chezmoi rule:

If a script is already running inside chezmoi apply, avoid launching chezmoi again from that script.

When you need chezmoi-specific information in a script, templating is often the better solution.

For my bootstrap flow, this gives me the best of both worlds:

  1. clone over HTTPS when no SSH key exists yet
  2. install SSH keys during the first apply
  3. automatically switch the dotfiles repo to SSH at the end

Exactly what I wanted.


If you use a similar bootstrap flow, hopefully, this saves you from the persistent state lock timeout.