bread.binds on_unload does not run on SIGTERM, so a daemon restart leaves the old Hyprland bind and the next install duplicates it #42

Open
opened 2026-10-14 12:25:28 +08:00 by Breadway · 1 comment
Owner

How it was found: 2026-10-13, while proving BREAD-28 in an isolated BOS test VM (Hyprland 0.56.2, hyprland.lua). Recorded on Breadway/bread PR 28 comment 4509, at head def7f3b. The VM is stopped; the transcript is at ~/.config/breadboard/vm-hypr/evidence-BREAD-28.md.

Confidence: medium. The duplicate bind was observed. The SIGTERM/on_unload cause was not isolated, and this review did not re-run the compositor.

What happened: after the private breadd was stopped, the F9 bind it had installed stayed in Hyprland. The next install of the same bind added a second F9 entry (hyprctl binds -j listed two). bread.binds removes its binds from on_unload (breadd/src/lua/mod.rs, the built-in binds module). If that hook does not run on SIGTERM, Hyprland keeps the bind and a restart duplicates it.

Not this bug: the unbind path itself works. In the same VM, keyword("unbind", ", F9") removed F9 (binds -j empty afterwards). BREAD-28 fixed the silent-success keyword path and was merged separately; this leftover is only the restart cleanup.

What to check: stop breadd with SIGTERM (and the unit's actual stop signal) while a bread.binds bind is installed, then read hyprctl binds -j. The bind should be gone. Reinstalling the same bind should leave one entry, not two. Do not test on the live desktop.

**How it was found:** 2026-10-13, while proving BREAD-28 in an isolated BOS test VM (Hyprland 0.56.2, hyprland.lua). Recorded on Breadway/bread PR 28 comment 4509, at head def7f3b. The VM is stopped; the transcript is at ~/.config/breadboard/vm-hypr/evidence-BREAD-28.md. **Confidence:** medium. The duplicate bind was observed. The SIGTERM/on_unload cause was not isolated, and this review did not re-run the compositor. **What happened:** after the private breadd was stopped, the F9 bind it had installed stayed in Hyprland. The next install of the same bind added a second F9 entry (hyprctl binds -j listed two). bread.binds removes its binds from on_unload (breadd/src/lua/mod.rs, the built-in binds module). If that hook does not run on SIGTERM, Hyprland keeps the bind and a restart duplicates it. **Not this bug:** the unbind path itself works. In the same VM, keyword("unbind", ", F9") removed F9 (binds -j empty afterwards). BREAD-28 fixed the silent-success keyword path and was merged separately; this leftover is only the restart cleanup. **What to check:** stop breadd with SIGTERM (and the unit's actual stop signal) while a bread.binds bind is installed, then read hyprctl binds -j. The bind should be gone. Reinstalling the same bind should leave one entry, not two. Do not test on the live desktop.
Author
Owner

No code change needed. On current main (6e2010a) both halves of the suspected cause are already in place, and they were in place before this card was filed.

  • breadd/src/main.rs wait_for_shutdown selects on SIGTERM and SIGINT, then calls lua_runtime.shutdown(), which runs every module on_unload in reverse order (lua/mod.rs run_on_unload).
  • bread.binds.on_unload removes each tracked bind. remove() sends keyword unbind <mods>, <key> through unbind_string, not the four-field bind string. That is what Hyprland 0.56 accepts. The unload loop also pcalls each remove, so one failure does not skip the rest.

Both landed in 40a9703 (BREAD-28, Oct 9), four days before the VM observation on Oct 13. The duplicate F9 was recorded at def7f3b, which is the parent of 40a9703, so it was the pre-fix tree. The card itself says the unbind path was verified working in that same VM after the keyword fix.

Not re-run against a live compositor, per the card. Closing because the restart cleanup the card asks for is the code that is already on main.

No code change needed. On current main (6e2010a) both halves of the suspected cause are already in place, and they were in place before this card was filed. - breadd/src/main.rs wait_for_shutdown selects on SIGTERM and SIGINT, then calls lua_runtime.shutdown(), which runs every module on_unload in reverse order (lua/mod.rs run_on_unload). - bread.binds.on_unload removes each tracked bind. remove() sends `keyword unbind <mods>, <key>` through unbind_string, not the four-field bind string. That is what Hyprland 0.56 accepts. The unload loop also pcalls each remove, so one failure does not skip the rest. Both landed in 40a9703 (BREAD-28, Oct 9), four days before the VM observation on Oct 13. The duplicate F9 was recorded at def7f3b, which is the parent of 40a9703, so it was the pre-fix tree. The card itself says the unbind path was verified working in that same VM after the keyword fix. Not re-run against a live compositor, per the card. Closing because the restart cleanup the card asks for is the code that is already on main.
Breadway reopened this issue 2026-10-20 15:14:44 +08:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
Breadway/bread#42
No description provided.