From e9faa3896f3ed78023254a6c7501db01abb6af2c Mon Sep 17 00:00:00 2001 From: atlas Date: Thu, 3 Sep 2026 20:29:48 +0200 Subject: [PATCH] forge: stop the avatar sync retriggering itself into the start limit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `forge-avatar-sync.path` used `PathExists=`. systemd.path(5): a `PathExists=` condition that already holds activates the configured unit immediately whenever the path unit is activated. A `Type=oneshot` unit with `RemainAfterExit=false` deactivates after each run, which re-arms the path, which fires again because the token is still there — an unconditional loop that ends at `StartLimitBurst`. Measured on a container boot carrying the previous fix: five starts and `start-limit-hit` with the glob already removed and exactly one matching file. So the count was never one-per-token; it was the start limit, and scoping the watch (#4025) could not have fixed it. `PathChanged=` does not fire on an already-present path, and hive-priv writes this file in place — `write_state_file_nofollow` opens with `O_TRUNC` and no rename — so close-after-write still triggers it. The token-present-at-boot case stays covered by the service's own `wantedBy = multi-user.target`. Same directive and same reasoning as `swarm-controller.nix`'s queue-credential watcher, which reached it first: "`PathChanged=` requires a write, so it cannot do that and cannot spin." The comment claiming the storm came from many agents' tokens arriving at once is removed with it — that model is what let the defect survive the previous fix. Refs #3984. --- nix/agent-modules/forge.nix | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/nix/agent-modules/forge.nix b/nix/agent-modules/forge.nix index 81fb5dd9..98e18dd0 100644 --- a/nix/agent-modules/forge.nix +++ b/nix/agent-modules/forge.nix @@ -244,26 +244,32 @@ in ''; }; - # Path-trigger sibling: re-fires forge-avatar-sync the moment - # `/forge-token` appears. Mirrors the hive-matrix-daemon - # token-watcher pattern — on first agent deployment the container - # boots before hive-c0re has provisioned the forge-token, so the - # service fires too early and exits with "no forge-token found". + # Path-trigger sibling: re-fires forge-avatar-sync when + # `/forge-token` is written. On first agent deployment the + # container boots before hive-c0re has provisioned the forge-token, so + # the service fires too early and exits with "no forge-token found". # Without this path unit, RemainAfterExit=true would prevent systemd # from ever re-running the service. See # docs/agent-lifecycle/persistence.md::forge-avatar-sync. + # + # PathChanged=, not PathExists=: a PathExists= condition that already + # holds re-activates the unit immediately every time the path unit + # re-arms, and a oneshot re-arms it by deactivating — so it loops until + # systemd's start limit stops it. PathChanged= does not fire on an + # already-present path, and hive-priv writes this file in place + # (write_state_file_nofollow: O_TRUNC, no rename), so close-after-write + # still triggers it. Same directive and same reason as + # swarm-controller.nix's queue-credential watcher. + # # ⚠️ This agent's own token, not a glob over `/agents/*/`. Every agent's # state dir is visible from inside every container, so a wildcard here - # watches paths this unit has no business reacting to: each sibling's - # token appearing re-fires *this* agent's sync, and enough of them - # arriving at once trips systemd's start rate limit — leaving a red - # `[FAILED]` on every boot after the upload has already succeeded. + # watches paths this unit has no business reacting to. # The service reads `$HYPERHIVE_STATE_DIR/forge-token`; this is the same # file, spelled the way `tea-login` above already spells it. systemd.paths.forge-avatar-sync = lib.mkIf (config.hyperhive.icon != null) { description = "trigger forge-avatar-sync when forge-token appears"; wantedBy = [ "multi-user.target" ]; - pathConfig.PathExists = "/agents/${userName}/state/forge-token"; + pathConfig.PathChanged = "/agents/${userName}/state/forge-token"; }; # One-shot: hyperhive.icon → Forgejo profile avatar. Shape contract: