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: