VT Switching not working on Wayland compositors

I’ve been trying to get VT switching to work on Hyprland, (and in the process have also tested sway and plasma/kde), but the Ctrl + Alt + F[1-12] binds to switch VTs aren’t working (and never have, as far as I know) on all three WM/DEs.

As far as I can tell, polkit isn’t allowing Wayland compositors to use the org.freedesktop.login1.chvt action on NixOS, which just works on other distros (I tested a fresh cachyos install just to be sure)

pkcheck --action-id org.freedesktop.login1.chvt --process 3600 # Hyprland
polkit\56result=auth_admin_keep
polkit\56retains_authorization_after_challenge=1
Authorization requires authentication and -u wasn't passed.

On other distros this ^ just returns with no message

Enabling logging for polkit as described on the wiki (Polkit - Official NixOS Wiki) gave information that I think might explain why it’s not working- a bunch of properties are null or false.

May 29 13:29:00 corais polkitd[3407]: action=[Action id='org.freedesktop.login1.chvt']
May 29 13:29:00 corais polkitd[3407]: subject=[Subject uid=1000 pid=3600 user='shaun' groups=users,wheel,tty,audio,video,networkmanager,libvirtd,input,pipewire,plugdev,ydotool,gamemode seat=null session=null system_unit=null local=false active=false]

But when I run the same command on the hyprland-start process that started the Hyprland one above:

pkcheck --action-id org.freedesktop.login1.chvt --process 3541 # hyprland-start
[no output, command succeeds]

All of those properties are populated.

May 29 13:39:24 corais polkitd[3407]: action=[Action id='org.freedesktop.login1.chvt']
May 29 13:39:24 corais polkitd[3407]: subject=[Subject uid=1000 pid=3541 user='shaun' groups=users,wheel,tty,audio,video,networkmanager,libvirtd,input,pipewire,plugdev,ydotool,gamemode seat='seat0' session='1' system_unit=null local=true active=true]

I haven’t been able to get confirmation whether other hyprland users on nixos experience the same thing or not. I really hope someone can explain what the problem is and how I can fix it since I’m way beyond my depth trying to debug the issue at this point.

I use Hyprland on NixOS and VT switching works without issue.

Do you have polkit enabled (security.polkit.enable = true) in your config?

Not manually, but it is enabled transitively.

Strange. I don’t know where to go with debugging from here.

@magicquark how do you launch hyprland?

Like this:

{ pkgs, ... }:
let
  user = "magicquark";
  program = "${pkgs.tuigreet}/bin/tuigreet";
  command = "start-hyprland > /dev/null";
in
{
  services = {
    greetd = {
      enable = true;
      settings = {
        initial_session = {
          inherit command user;
        };
        default_session = {
          command = ''${program} --asterisks --remember --remember-user-session --time --cmd "${command}"'';
          inherit user;
        };
      };
    };
  };
}

Strange, that’s basically how I have mine set up. security.polkit.extraConfig doesn’t reference the org.freedesktop.login1.chvt action at all?

It does not.

I enabled the logging on my system and switched to a different VT and the logs showed no null properties, so this does look like what is at issue here.

Hmm.. I’m not sure what could cause those to get nulled out. I guess does youre logind session include anything in its CGroup that mine doesn’t?

shaun@corais ~> loginctl session-status "$XDG_SESSION_ID"
3 - shaun (1000)
  Since: Sun 2026-05-31 18:39:18 PDT; 20min ago
  State: active
 Leader: 3567 (greetd)
   Seat: seat0; vc1
    TTY: tty1
 Remote: no
Service: greetd
   Type: wayland
  Class: user
   Idle: no
   Unit: session-3.scope
         ├─3567 /nix/store/5wpl6l86y4w7bdsc5s30qp7mpj4x6b03-greetd-0.10.3/bin/greetd --session-worker 12
         ├─3668 start-hyprland
         └─3890 Xwayland :0 -rootless -core -listenfd 54 -listenfd 55 -displayfd 139 -wm 136
shaun@corais ~> systemctl status session-3.scope
● session-3.scope - Session 3 of User shaun
     Loaded: loaded (/run/systemd/transient/session-3.scope; transient)
  Transient: yes
     Active: active (running) since Sun 2026-05-31 18:39:18 PDT; 22min ago
 Invocation: e2c3a861065c44b29515c13e2b4464e3
         IP: 0B in, 0B out
         IO: 130.3M read, 84K written
      Tasks: 15
     Memory: 372M (peak: 374.4M)
        CPU: 7.254s
     CGroup: /user.slice/user-1000.slice/session-3.scope
             ├─3567 /nix/store/5wpl6l86y4w7bdsc5s30qp7mpj4x6b03-greetd-0.10.3/bin/greetd --session-worker 12
             ├─3668 start-hyprland
             └─3890 Xwayland :0 -rootless -core -listenfd 54 -listenfd 55 -displayfd 139 -wm 136

May 31 18:39:18 corais systemd[1]: Started Session 3 of User shaun.

I have this in addition to what you have, but I think it is unrelated.

/etc/profiles/per-user/magicquark/bin/Hyprland --watchdog-fd 4

For further debugging, try setting debug.disable_logs = false in your Hyprland configuration, then try to switch to another VT. The logs should be in $XDG_RUNTIME_DIR/hypr/…/hyprland.log.

On my system in the logs I see:

DEBUG ]: Switching from VT 0 to VT 2
DEBUG from aquamarine ]: drm: Restoring after VT switch

You might see some errors from aquamarine that help shed some light on the issue.

Actually I think that is the issue. On my end, the Hyprland process that gets started by start-hyprland isn’t being recognized as part of the session, which causes polkit to not recognize it, so it tries to require authentication and that fails, which shows up as an error in my hyprland log - ERR from aquamarine ]: [libseat] [libseat/backend/logind.c:202] Could not switch session: Access denied as the requested operation requires interactive authentication. However, interactive authentication has not been enabled by the calling program.

The question then becomes why is systemd not including the Hyprland process in the session.

UPDATE: apparently it was ananicy causing it, or at least how I had it configured. Removing services.ananicy.enable = true; fixed it. It looks like it messes with cgroups, so it makes some sense at least.

I dug a bit further into why ananicy breaks vt switching in 26.01. The issue seams to be that the hyprland process spawned by start-hyprland is moved into the root cgroup, which takes it out of the user session slice cgroup.

This happens because of the cgroup_realtime_workaround of ananicy-cpp. This workaround takes realtime scheduling tasks and moves them to the root cgroup to be able to move them to the ananicy defined ones.

So the fix for this is to set services.ananicy.settings.cgroup_realtime_workaround = lib.mkForce false;.

What puzzles me is that the workaround was already present and enabled in 25.11 but didn’t cause an issue then.