Repository navigation
fix(daemon): handle daemons not spawned by atuin - #4516
Merged
Merged
Conversation
Contributor
[Medium impact] The PR appears safe to merge, with no new actionable issues found. SummaryThe PR records who runs the daemon and leaves managed daemons to their service manager.
Reviews (4) · Last reviewed commit: "fix(daemon): wait for a restarted daemon..." · Reviewed by Greptile |
This was referenced Oct 22, 2026
atuin-bot
pushed a commit
that referenced
this pull request
Oct 22, 2026
`atuin daemon start --force` evicts the running daemon before starting its own, and it killed the PID in the pidfile even when no daemon held the pidfile. A daemon that crashed leaves its pidfile behind, and its PID may since have gone to another process. Evicting now signals the PID only while a daemon holds the pidfile. The regression test dies by SIGTERM without the fix. ## Stack (bottom → top) 1. #4512 fix(daemon): spare a stale pidfile's PID when evicting 2. #4513 feat(common): parse a process's cgroups under cgroup v1 and v2 3. #4514 refactor(daemon): keep where the daemon listens behind service::address 4. #4515 refactor(daemon): take up the daemon's socket in SocketHolder, not Backend 5. #4516 feat(daemon): leave a daemon that something else runs to it 6. #4517 feat(daemon): run the daemon as a launchd agent on macOS Each PR includes the ones below it. Review and merge bottom-up.
markovejnovic
force-pushed
the
feat/daemon-run-mode
branch
3 times, most recently
from
October 22, 2026 01:56
736e149 to
9c8e447
Compare
atuin-bot
pushed a commit
that referenced
this pull request
Oct 22, 2026
`CgroupV2` finds a process's cgroup in cgroup v2's unified hierarchy, the `0::` line of `/proc/<pid>/cgroup`, and `CgroupV1` the one in the v1 hierarchy a controller or name is bound to, such as systemd's `name=systemd`. The hybrid layout lists both. RHEL 8 and its rebuilds (systemd 239, built with `-Ddefault-hierarchy=legacy`) boot cgroup v1 alone, with no `0::` line: the kernel lists v2's hierarchy only once something mounts it. Nothing calls them yet: a later PR in this stack uses them to tell whether a systemd user unit runs the daemon. `listed_in`'s cases pass in a standalone crate on macOS. The `reads_the_active_process` tests were not run on this version. ## Stack (bottom → top) 1. #4512 fix(daemon): spare a stale pidfile's PID when evicting 2. #4513 feat(common): find a process's cgroup under cgroup v1 and v2 3. #4514 refactor(daemon): keep where the daemon listens behind service::address 4. #4515 refactor(daemon): take up the daemon's socket in SocketHolder, not Backend 5. #4516 feat(daemon): leave a daemon that something else runs to it 6. #4517 feat(daemon): run the daemon as a launchd agent on macOS Each PR includes the ones below it. Review and merge bottom-up.
atuin-bot
pushed a commit
that referenced
this pull request
Oct 22, 2026
…ss (#4514) Unix sockets and Windows' TCP port were told apart by cfgs across `service/`. Each platform's `address` module now defines the same names, so the code calling it has no cfgs of its own. The commit lists what moved where. No behaviour change. Windows clients now pass `--daemonize` as well, which does nothing there. Some public items moved: - `service::pidfile::SocketPathError` is now `service::address::LocateError`, and `Pidfile::socket_path` is now `DaemonAddress::locate`. - `client::potential_socket_paths` is gone, and so is the `service::RemoveSocketError` re-export; use `service::address::RemoveSocketError`. - The pidfile's round-trip proptest now runs on Windows too, over UTF-8 paths only. `AutoOsString`'s own tests round-trip the others. Includes #4513, so review only the last commit. - macOS: clippy (default and `--tests`), rustdoc, `atuin-daemon` lib tests and `e2e_daemon` pass. - Linux (Docker): clippy and the `atuin-common` and `atuin-daemon` lib tests pass. - Windows (`x86_64-pc-windows-gnu`, cross-compiled): clippy is clean for `atuin-daemon` and `cargo check --tests` passes. Clippy on the `atuin` binary fails with the same 10 errors as main. Not run on Windows itself. ## Stack (bottom → top) 1. #4512 fix(daemon): spare a stale pidfile's PID when evicting 2. #4513 feat(common): find a process's cgroup under cgroup v1 and v2 3. #4514 refactor(daemon): keep where the daemon listens behind service::address 4. #4515 refactor(daemon): take up the daemon's socket in SocketHolder, not Backend 5. #4516 feat(daemon): leave a daemon that something else runs to it 6. #4517 feat(daemon): run the daemon as a launchd agent on macOS Each PR includes the ones below it. Review and merge bottom-up.
atuin-bot
pushed a commit
that referenced
this pull request
Oct 22, 2026
…ckend (#4515) `Backend::listen` ran inside the daemon, but backends belong to the client, which picks one to start and stop the daemon. Off Linux, `ExternalSystemd` built a `Subproc` only to bind its socket. `SocketHolder` now says who holds the socket, the daemon or systemd, and takes it up. Taking systemd's socket moves into `systemd/`, which off Linux has the daemon bind its own. No behaviour change. `ExternalSystemd` keeps its `cfg(unix)` gate until the next PR in this stack replaces it. Includes #4513 and #4514, so review only the last commit. Checked the same way as #4514. ## Stack (bottom → top) 1. #4512 fix(daemon): spare a stale pidfile's PID when evicting 2. #4513 feat(common): find a process's cgroup under cgroup v1 and v2 3. #4514 refactor(daemon): keep where the daemon listens behind service::address 4. #4515 refactor(daemon): take up the daemon's socket in SocketHolder, not Backend 5. #4516 feat(daemon): leave a daemon that something else runs to it 6. #4517 feat(daemon): run the daemon as a launchd agent on macOS Each PR includes the ones below it. Review and merge bottom-up.
markovejnovic
force-pushed
the
feat/daemon-run-mode
branch
from
October 22, 2026 02:43
9c8e447 to
a296f93
Compare
markovejnovic
marked this pull request as ready for review
October 22, 2026 03:14
Member
Author
|
/merge |
Collaborator
|
Landed as d923707. GitHub shows this merged once it reaches main. |
Only `daemon.systemd_socket` told clients that something other than Atuin runs the daemon. A daemon that `brew services`, a systemd user unit or another launchd job ran was taken for one a client had forked. A client of another build replaced it, behind the back of a manager that would start its own again. ## What changes for users Only a daemon that launchd or a systemd user service started, which is what `brew services` runs on both platforms, behaves differently: | | Before | Now | |---|---|---| | A newer client finds the daemon outdated (with `daemon.autostart`), or you run `atuin daemon restart` | The client stops it and forks its own, racing the manager. If the client's daemon wins, the manager keeps starting its own again, and each one exits on the taken pidfile. | The client has launchd (`launchctl kickstart -k`) or systemd (`systemctl --user restart`) restart it, then waits for its own build. The daemon stays the manager's. | | The same, but the manager runs another atuin than the client, such as `brew services` with a curl-installed atuin earlier on `PATH` | The client replaces it with its own, fighting the manager the same way. | The client refuses: "what runs the daemon starts ..., not this atuin at ...". Search fails with that, and the shell hook saves history locally, as when the daemon is down. The daemon stays outdated until it's restarted through the manager. | | `atuin daemon stop` | Shuts it down, and launchd starts it straight back. | Refuses, naming the manager's command (`brew services stop atuin`, `systemctl --user stop <unit>`). | | `atuin daemon start --force` | Kills it and takes its place, and the manager keeps starting its own again. | Logs the refusal without killing it, then fails on the pidfile the daemon still holds. |
atuin-bot
force-pushed
the
feat/daemon-run-mode
branch
from
October 22, 2026 04:32
c07920b to
d923707
Compare
atuin-bot
pushed a commit
that referenced
this pull request
Oct 22, 2026
This PR migrates the default macOS daemon path to go through `launchd`, removing the "Process running in background" chip in the Dock on Golden Gate. ## User-facing changes On macOS, with `daemon.autostart = true`, a client of the user at the console installs and starts the launchd agent `sh.atuin.daemon` instead of forking a daemon. That's only for the default profile (no `ATUIN_HOME` or `ATUIN_CONFIG_DIR`) in the user's own home. | On macOS, with autostart | Before | Now | |---|---|---| | Starting the daemon | The first command after login forks `atuin daemon start --daemonize`, which the Dock shows as a process running in the background. | The first command that needs the daemon installs the agent as `~/Library/LaunchAgents/sh.atuin.daemon.plist`, which starts it right away, and waits for it to answer, as it waits for a forked daemon. From then on, launchd starts it at each login, and whenever a client connects to its socket while it isn't running. macOS says once that it added a background item, and lists it as `atuin` under System Settings > General > Login Items. | | Upgrading Atuin, or `atuin daemon restart` | The client stops the old daemon and forks its own build. | The client boots the agent out and installs it again, running the client's own executable. The first command of a new build replaces a forked daemon with the agent the same way. | | The Login Items switch is off | No such switch. | Atuin leaves the agent off and works without the daemon until the switch is back on. | | `atuin daemon stop` | Stops the daemon, and the next command that needs it forks another. | Boots the agent out, and the next command that needs it installs and starts it again. | | `atuin config disable daemon` | Stops the daemon. | Also removes the agent, which launchd would otherwise start at the next login. | | `atuin daemon start` in a terminal | Runs the daemon in the foreground. | Refuses while launchd listens on the agent's socket, as it does until `atuin daemon stop`. `--force` boots the agent out first. | | Another launchd job, such as `brew services`', runs the daemon | Since #4516, clients leave it to that job. | The daemon also removes Atuin's agent when it starts, which would otherwise fight it for the pidfile at every login. | | The agent fails to start | - | `daemon.stderr.log` in Atuin's logs directory says why. | On every platform, a daemon socket that accepts connections but never answers, as a hung daemon's does, or launchd's while its agent fails to boot, no longer hangs clients: | | Before | Now | |---|---|---| | Most commands, such as `atuin daemon status` or search | Wait on it indefinitely (`history start` gives up after 2s). | Give up with "the daemon accepted the connection but didn't answer within ...ms": after `local_timeout` plus 2s (4s by default) while a daemon holds the pidfile, and after 500ms while none does. | | `atuin daemon stop` and `restart` | Wait on it indefinitely. | Evict it, and `restart` starts a new one. | | A client with autostart | Waits on it. | Replaces it, if no daemon holds the pidfile. | Unchanged: - Linux and Windows, apart from the unanswered socket above. - macOS with `ATUIN_HOME` or `ATUIN_CONFIG_DIR` set, or without the console, such as over SSH while logged out of the desktop: clients fork the daemon as before. An agent that already runs is controlled through launchd from any of the user's sessions. - macOS with autostart off: Atuin installs no agent. One installed while autostart was on is still Atuin's to stop and remove. - The uninstall docs gain a step that removes the agent.
atuin-bot
pushed a commit
that referenced
this pull request
Oct 22, 2026
### Bug Fixes - *(ai)* Import IntoStaticStr from strum_macros, not strum ([#4480](#4480)) - *(ai)* Improve detection of git repositories ([#4450](#4450)) - *(client)* Resolve relative config paths beside config.toml, not the cwd ([#4479](#4479)) - *(config)* Don't print errors twice ([#4501](#4501)) - *(daemon)* Fetch the server's capabilities until a fetch succeeds - *(daemon)* Spare a stale pidfile's PID when evicting ([#4512](#4512)) - *(daemon)* Handle daemons not spawned by atuin ([#4516](#4516)) - *(output)* Keep quoted words together in output search ([#4502](#4502)) - *(packfile)* Load only the records inside a manifest's range - *(pty-proxy)* Standalone init with `atuin pty-proxy init` ([#4449](#4449)) - *(pty-proxy)* Wait for input on a non-blocking inherited stdin ([#4486](#4486)) - *(pty-proxy)* Don't re-exec shells running commands from a string or file ([#4414](#4414)) - *(search)* Don't panic when copying with no results ([#4426](#4426)) - *(server)* Let `ATUIN_*` environment variables override server.toml ([#4456](#4456)) - *(update)* Accept ATUIN_GITHUB_TOKEN ([#4262](#4262)) - Remove duplicated word in the no-history stats message ([#4451](#4451)) - Error printing in `?` handler ([#4448](#4448)) - Normalize relative gitdir paths in worktree resolution ([#4506](#4506)) - Upgrade regressions found in release QA ([#4519](#4519)) ### Documentation - Fix ctrl_n_shortcuts default value ([#4458](#4458)) - List all history list --format variables ([#4460](#4460)) - Fix the --reverse default for history list ([#4472](#4472)) - Mention December default in wrapped --year help ([#4476](#4476)) ### Features - *(common)* AbsPath and AbsPathBuf, Path and PathBuf checked to be absolute ([#4481](#4481)) - *(common)* Find a process's cgroup under cgroup v1 and v2 ([#4513](#4513)) - *(daemon)* Run the daemon as a launchd agent on macOS ([#4517](#4517)) - *(pty-proxy)* Window title follows child process ([#4447](#4447)) - *(secrets)* Keep HashiCorp tokens out of history ([#4445](#4445)) ### Miscellaneous Tasks - Give mono its own nixfmt step and scope atuin's to atuin/ - Say a /merge's landed commit gets CI on main - Rebuild the apt .deb cache for Ubuntu 24.04 ([#4504](#4504)) - Remove the benchmarks ([#4511](#4511)) ### Performance - *(common)* Compile the vendor regexes once ([#4485](#4485)) - *(common)* Drop the tree watcher's file-id cache ([#4492](#4492)) - *(common)* Release a line reader's buffer at the end of the file ([#4494](#4494)) - *(common)* Keep no tree watcher entry for a rejected file ([#4493](#4493)) - *(daemon)* Warm up only a resumed session's unanswered calls ([#4490](#4490)) - *(packfile)* Compress packs in one shot ([#4484](#4484)) ### Refactor - *(daemon)* Managed daemon service with multiple backends ([#4488](#4488)) - *(daemon)* Move daemon startup into the service module ([#4503](#4503)) - *(daemon)* Keep where the daemon listens behind service::address ([#4514](#4514)) - *(daemon)* Take up the daemon's socket in SocketHolder, not Backend ([#4515](#4515)) - *(deps)* Bump clap_complete_nushell from 4.6.0 to 4.6.2 ([#4473](#4473)) - *(deps)* Bump clap_complete from 4.6.9 to 4.6.11 ([#4396](#4396)) - *(deps)* Bump ratatui from 0.30.0 to 0.30.2 ([#4474](#4474)) - *(deps)* Bump certifi from 2025.11.12 to 2026.10.19 in /docs in the uv group across 1 directory ([#4500](#4500)) - *(pty-proxy)* Run on tokio with the parser on its own runtime ([#4482](#4482)) - *(pty-proxy)* Forward captures to the daemon on the proxy's runtime ([#4487](#4487)) - *(pty-proxy)* Rename the screen's parser to Emulator ([#4510](#4510)) - *(pty-proxy)* Migrate output parsing to vte ([#4518](#4518)) ### Testing - *(daemon)* Run the output index tests under rstest ([#4496](#4496))
1 task
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Only
daemon.systemd_sockettold clients that something other than Atuin runs the daemon. A daemon thatbrew services, a systemd user unit or another launchd job ran was taken for one a client had forked. A client of another build replaced it, behind the back of a manager that would start its own again.What changes for users
Only a daemon that launchd or a systemd user service started, which is what
brew servicesruns on both platforms, behaves differently:daemon.autostart), or you runatuin daemon restartlaunchctl kickstart -k) or systemd (systemctl --user restart) restart it, then waits for its own build. The daemon stays the manager's.brew serviceswith a curl-installed atuin earlier onPATHatuin daemon stopbrew services stop atuin,systemctl --user stop <unit>).atuin daemon start --force