Skip to content

service: fix caveat for SSH sessions - #23464

Open
IngmarStein wants to merge 1 commit into
Homebrew:mainfrom
IngmarStein:fix-service-running-ssh
Open

service: fix caveat for SSH sessions#23464
IngmarStein wants to merge 1 commit into
Homebrew:mainfrom
IngmarStein:fix-service-running-ssh

Conversation

@IngmarStein

Copy link
Copy Markdown
Contributor

When connected via SSH, launchctl list <label> only searches the system domain, so it won't find services loaded in the gui/<uid> domain. This caused Utils::Service.running? to incorrectly return false for running services, which in turn made brew upgrade print "brew services start" instead of the correct "brew services restart".

Fix by using launchctl print <domain>/<label> (which works across sessions) for the gui and user domains, falling back to the bare launchctl list <label> for backward compatibility. This mirrors how FormulaWrapper#status_output_success_type already checks service status for brew services.

Output for a running service over SSH before this PR:

❯ brew info redis
==> redis ✔: stable 8.10.0 (bottled), HEAD
Persistent key-value database, with built-in net interface
[…]
==> Caveats
To start redis now and restart at login:
  brew services start redis
Or, if you don't want/need a background service you can just run:
  /opt/homebrew/opt/redis/bin/redis-server /opt/homebrew/etc/redis.conf

Output with this PR:

❯ brew info redis
==> redis ✔: stable 8.10.0 (bottled), HEAD
Persistent key-value database, with built-in net interface
[…]
==> Caveats
To restart redis after an upgrade:
  brew services restart redis
Or, if you don't want/need a background service you can just run:
  /opt/homebrew/opt/redis/bin/redis-server /opt/homebrew/etc/redis.conf

  • Have you followed our Contributing guidelines?
  • Have you checked for other open Pull Requests for the same change?
  • Have you explained what your changes do? Performance claims (e.g. "this is faster") must include Hyperfine benchmarks.
  • Have you explained why you'd like these changes included, not just what they do?
  • For bug fixes, have you given step-by-step brew commands to reproduce the bug?
  • Have you written new tests (excluding integration tests)? Here's an example.
  • Have you successfully run brew lgtm (style, typechecking and tests) locally?

  • I did not use AI/LLM to create this PR, or I disclosed the tool/model below and reviewed its output; I did not attribute commits to AI and will answer maintainer questions and review comments myself without AI/LLM.

Claude Opus 5 was used to find the discrepancy between brew info and brew services and create the patch.


Copilot AI lite review requested due to automatic review settings August 6, 2026 19:39

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes service-status detection on macOS when Homebrew is run over SSH, so Utils::Service.running? can correctly detect services loaded in gui/<uid> or user/<uid> domains and produce the right caveats (e.g., suggesting brew services restart after upgrades).

Changes:

  • Update Utils::Service.running? to use launchctl print <domain>/<label> for gui/user domains, with a fallback to launchctl list <label> for compatibility.
  • Add unit tests covering the running? decision paths for “no manager”, systemctl, and launchctl behaviour.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
Library/Homebrew/utils/service.rb Switches macOS service detection to domain-aware launchctl print, with compatibility fallback.
Library/Homebrew/test/utils/service_spec.rb Adds specs for Utils::Service.running? across manager availability and launchctl domain probing.
Suppressed comments (1)

Library/Homebrew/test/utils/service_spec.rb:56

  • This spec also interpolates the real Process.uid/Process.euid into its expectations. If the suite is ever executed as root (Process.euid.zero?), Utils::Service.running? won’t try the gui/user domains and this example will fail. Stubbing Process.uid/Process.euid makes the behaviour under test deterministic.
    it "stops trying after first successful domain" do
      f = formula do
        T.bind(self, T.class_of(Formula))
        url "foo-1.0"
      end

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread Library/Homebrew/test/utils/service_spec.rb Outdated
@IngmarStein
IngmarStein force-pushed the fix-service-running-ssh branch from 59adf5b to 2a8c5e5 Compare August 6, 2026 20:41
Comment thread Library/Homebrew/utils/service.rb Outdated
Comment thread Library/Homebrew/utils/service.rb Outdated
@IngmarStein
IngmarStein force-pushed the fix-service-running-ssh branch 2 times, most recently from 97e5c1b to 09097ab Compare August 7, 2026 10:11

@MikeMcQuaid MikeMcQuaid left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. brew tests failing.

Comment thread Library/Homebrew/utils/service.rb Outdated
Comment thread Library/Homebrew/utils/service.rb Outdated
Comment on lines +12 to +23
if (launchctl = self.launchctl)
label = formula.plist_name

# When connected via SSH, `launchctl list <label>` only searches
# the system domain, so it won't find services loaded in the GUI
# domain. Use `launchctl print <domain>/<label>` which works
# across sessions, falling back to the bare label for backward
# compatibility.
candidate_domains = Homebrew::Services::System.candidate_domain_targets

candidate_domains.any? { |domain| quiet_system(launchctl, "print", "#{domain}/#{label}") } ||
quiet_system(launchctl, "list", label)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How does this differ/overlap with status_output_success_type in formula_wrapper.rb or find_launchctl_with_wildcard/uninstall_launchctl in abstract_uninstall.rb or running in services/cli.rb?

It looks like they are all doing pretty similar things but in different ways that will have different results. Feels like we need a single source of truth for "find this service" or "is this service running" across all locations or it'll just be whack-a-mole with behaviour differing depending on where it's run.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The use cases seem slightly different across the call sites: checking whether a service running, extracting the status from the output, listing services. I've consolidated a bit more, ptal.

@IngmarStein
IngmarStein force-pushed the fix-service-running-ssh branch 3 times, most recently from 8500bc0 to a987d7f Compare August 7, 2026 14:29
When connected via SSH, `launchctl list <label>` only searches the
system domain, so it won't find services loaded in the `gui/<uid>`
domain. This caused `Utils::Service.running?` to incorrectly return
false for running services, which in turn made `brew upgrade` print
"brew services start" instead of the correct "brew services restart".

Consolidate the "is this service running?" logic into a single source
of truth: `Homebrew::Services::System.launchctl_service_running?`.
This iterates `candidate_domain_targets` to try domain-qualified
`launchctl print <domain>/<label>` before falling back to a bare
`launchctl list <label>`. Both `Utils::Service.running?` and
`FormulaWrapper#loaded?` now use this method.

Refactor `FormulaWrapper#status_output_success_type` to also iterate
`candidate_domain_targets` instead of only trying the single
`domain_target`, which over SSH returns `user/<euid>` and misses
services loaded in the `gui/<uid>` domain.
@IngmarStein
IngmarStein force-pushed the fix-service-running-ssh branch from a987d7f to cd98005 Compare August 7, 2026 14:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants