Hl.dsp.workspace.change_id not updating status bar?

I implemented something along the lines of what this old discussion suggested, but in Lua:

local function swapactiveworkspacewith(target)
    local source = hl.get_active_workspace()
    if target == nil or source.id == target then
        return
    end
    local source_id = source.id

    hl.dispatch(hl.dsp.workspace.change_id({ workspace = source, id = 42 })) -- arbitrary temporary ID
    hl.notification.create({ text = "sent source to 42", timeout = 5000 }) -- debugging

    hl.dispatch(hl.dsp.workspace.change_id({ workspace = target, id = source_id }))
    hl.notification.create({ text = "sent target to source", timeout = 5000 })

    hl.dispatch(hl.dsp.workspace.change_id({ workspace = 42, id = target }))
    hl.notification.create({ text = "sent source to 42", timeout = 5000 })
end

Side note: I’m not sure whether this is particularly idiomatic, but I couldn’t find a way to iterate over all windows in a specific workspace.

I’d like to avoid having to move every window individually with SUPER+SHIFT+<number>, as suggested in another forum reply that I can’t find now for some reason…

The function itself works correctly. hyprctl activeworkspace reports the expected workspace ID after the swap. However, my status bar’s workspace switcher does not update.

Both my custom Quickshell configuration and the stock ashell exhibit the same behaviour; they continue to report the previous workspace ID even though Hyprland reports the new one.

Here’s a short video demonstrating the issue: https://streamable.com/t5ka9f

The sequence of events displayed in the video is:

  1. I launch OBS on workspace 1.
  2. I switch to workspace 3.
  3. I open a terminal on workspace 3.
  4. I run hyprctl activeworkspace, which correctly reports workspace 3.
  5. I invoke my swapactiveworkspacewith(1) key bind. The notifications in the top-right confirm that the function executes.
  6. I run hyprctl activeworkspace again, which correctly reports workspace 1.
  7. However, the status bar still reports that I am on workspace 3. It should now report workspace 1.
  8. I switch to workspace 5, which is empty.
  9. I switch to workspace 3, which now contains OBS.
  10. I briefly switch to workspace 1, which now contains the terminal.
  11. I switch back to workspace 3.
  12. I stop the recording.

The same behaviour occurs with ashell.

While writing this post, I found that adding the following to the end of my function causes the status bar to update:

--- original    2026-08-08 03:07:26.565765620 +0200
+++ new         2026-08-08 03:07:34.153765170 +0200
@@ -13,4 +13,7 @@

     hl.dispatch(hl.dsp.workspace.change_id({ workspace = 42, id = target }))
     hl.notification.create({ text = "sent source to 42", timeout = 5000 })

+    hl.dispatch(hl.dsp.focus({ workspace = source_id }))
+    hl.dispatch(hl.dsp.focus({ workspace = target }))
 end

However, this causes visible flickering because the focus is changed twice. Alas, my questions:

  1. Why does changing the workspace IDs with change_id not cause the active workspace event used by status bars to update?
  2. Is there a way to notify the status bar that the active workspace has changed without changing focus twice?
  3. Or is there a more idiomatic way to swap the windows of two workspaces

Thanks in advance!

I found a link to it in my clipboard manager if anyone’s interested: http://forum.hypr.land/t/moving-closing-all-windows-in-current-workspace/1662/2

Also, I noticed that the notification text is incorrect, it should be this:

--- original	2026-08-08 03:07:34.153765170 +0200
+++ new	2026-08-08 03:27:27.573694443 +0200
@@ -12,7 +12,7 @@
     hl.notification.create({ text = "sent target to source", timeout = 5000 })
 
     hl.dispatch(hl.dsp.workspace.change_id({ workspace = 42, id = target }))
-    hl.notification.create({ text = "sent source to 42", timeout = 5000 })
+    hl.notification.create({ text = "sent source to target", timeout = 5000 })
 
     hl.dispatch(hl.dsp.focus({ workspace = source_id }))
     hl.dispatch(hl.dsp.focus({ workspace = target }))

Not editing the original post because the video I posted show the incorrect notification.