Skip to content

shell -d silently no-ops (exit 0) when an instance is still running, making kill/restart racy #139

Description

@dadams-AU

Summary

caelestia shell -d exits 0 without starting a shell when another instance is still running, printing only An instance of this configuration is already running. to stdout. This makes the common kill; sleep; shell -d restart idiom racy, and it fails in the worst possible situation: restarting the shell while the session is locked.

Details

subcommands/shell.py builds the launch args as:

args = ["qs", "-c", "caelestia", "-n"]
...
if self.args.daemon:
    args.append("-d")
    subprocess.run(args)

qs -n / --no-duplicate means "Exit immediately if another instance of the given config is running." So when a previous instance has been asked to quit but has not finished tearing down, shell -d no-ops. Because it's a daemon launch there is no terminal attached, the message goes nowhere, and subprocess.run's non-zero-ness is never checked — the caller sees success.

Why this is more than cosmetic

The documented-ish way to restart the shell from a compositor keybind is:

qs -c caelestia kill; sleep .3; caelestia shell -d

Shell shutdown is not instant, and it is slowest while a session lock is up (the lock surface and its screencopy/blur have to be torn down). On my machine a full kill → restart round trip measures ~1.5s, so a 0.3s sleep loses the race.

When it loses the race while locked, the result is that Hyprland is left holding an ext-session-lock with no client: the fallback "lock error" screen, no password prompt, and no shell to restart — recoverable only via a TTY or a separate lock client. The keybind that exists specifically to rescue a broken lock is the thing that guarantees you stay locked out.

Log from the dead instance, showing a clean exit (not a crash) followed by nothing coming back:

2026-07-30 09:16:57.894  INFO: Exiting due to IPC request.

Reproduction

With a shell already running:

$ caelestia shell -d; echo "exit: $?"
An instance of this configuration is already running.
exit: 0

Suggested fixes

Any one of these would remove the footgun:

  1. Propagate the failure — return qs's exit status from shell -d so kill; sleep; shell -d can be written to retry.
  2. Add caelestia shell -r/--restart that kills, waits for the instance to actually disappear (qs list --all), then starts. This is the operation people actually want, and it's the one that needs to be race-free.
  3. Drop the hardcoded -n (or put it behind a flag). For an emergency restart, briefly having two instances is strictly better than having zero.

Happy to send a PR for (2) if you'd like — it's the variant that fixes the lock-out case.

Versions

  • caelestia-cli-git 1.1.2.r5.g2311754-1
  • caelestia-shell-git 2.2.0.r17.gfbb3d1b-1
  • quickshell-git 0.3.0.r19.g43d4fa9-1
  • Hyprland 0.56.1

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions