start the forge and github pollers when their token lands late
Both pollers wait about ten minutes for the agent's token, then exit 0. Under Restart=on-failure that exit is final, and nothing started them again, so a token minted or placed after the window left the agent without notifications until its container restarted. Add a path unit per poller with PathChanged on the token file(s) the poller reads, the shape forge-avatar-sync already uses. A path unit starts its service only when it is not running, so a poller that found its token is untouched. The github path unit is gated on github.enable like its service. Container-side: it ships with the agent's next rebuild, and an agent on an older rev keeps the old behaviour. Closes #6179
This commit is contained in:
parent
0c78ccbf84
commit
fae9398295
6 changed files with 70 additions and 1 deletions
|
|
@ -133,6 +133,12 @@ unread-only filter), formats each notification as a broker
|
|||
`Wake { from: "forge" }` message, and delivers it to the agent's own
|
||||
inbox so claude's normal turn loop picks it up.
|
||||
|
||||
The daemon waits about ten minutes for the agent's forge token, then exits 0,
|
||||
which its `Restart=on-failure` doesn't restart. A path unit
|
||||
(`hive-forge-notify.path`) starts it again when the fetched token file or the
|
||||
state-dir `forge-token` changes, so a token minted later still starts the
|
||||
polling.
|
||||
|
||||
The crate builds a second, independent binary for a different host — see
|
||||
[github.md](github.md#notifications).
|
||||
|
||||
|
|
|
|||
|
|
@ -110,7 +110,10 @@ crate its build cache.
|
|||
At runtime the poller needs the PAT above. No PAT, no polling: the unit
|
||||
logs why and exits 0, which is why it's `Restart = on-failure` and
|
||||
never `always` — a clean exit on a PAT-less agent must not become a
|
||||
restart loop. Once polling, it re-reads `<state>/github-token` on every
|
||||
restart loop. The poller waits about ten minutes for the PAT to appear
|
||||
before it gives up. A path unit (`hive-github-notify.path`) starts the poller
|
||||
again when `<state>/github-token` changes, so a PAT that lands later still
|
||||
enables polling. Once polling, it re-reads the file on every
|
||||
tick, so it picks up a rotated PAT on the next tick without a restart.
|
||||
|
||||
Forgejo models its notifications API on GitHub's, so one tolerant
|
||||
|
|
|
|||
|
|
@ -181,6 +181,21 @@ in
|
|||
};
|
||||
};
|
||||
|
||||
# Starts the poller when a token appears after it gave up waiting for one
|
||||
# (the poller waits about ten minutes, then exits 0, which `on-failure`
|
||||
# does not restart). A path unit starts its service only when it is not
|
||||
# running, so it costs nothing while the poller runs. Both files the poller
|
||||
# reads are watched. `PathChanged=` for the reason given on
|
||||
# `forge-avatar-sync` below.
|
||||
systemd.paths.hive-forge-notify = {
|
||||
description = "start hive-forge-notify when the forge token appears";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
pathConfig.PathChanged = [
|
||||
fetchedTokenFile
|
||||
stateTokenFile
|
||||
];
|
||||
};
|
||||
|
||||
# Path-trigger sibling: re-fires forge-avatar-sync when the fetched
|
||||
# forge token (./forge-token.nix) appears or is replaced. On first
|
||||
# agent deployment the container can boot before the swarm has minted
|
||||
|
|
|
|||
|
|
@ -127,5 +127,18 @@ in
|
|||
Group = userName;
|
||||
};
|
||||
};
|
||||
|
||||
# Starts the poller when the token appears after it gave up waiting for one
|
||||
# (about ten minutes, then exit 0, which `on-failure` does not restart).
|
||||
# The operator places the PAT through the swarm after the agent runs, so
|
||||
# that is the usual order. A path unit starts its service only when it is
|
||||
# not running. `PathChanged=` because the fetch renames the file into place,
|
||||
# and a `PathExists=` that already holds would re-fire on every re-arm.
|
||||
# Gated on the service it triggers.
|
||||
systemd.paths.hive-github-notify = lib.mkIf config.services.hyperhive.agent.github.enable {
|
||||
description = "start hive-github-notify when the GitHub token appears";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
pathConfig.PathChanged = "/agents/${userName}/state/github-token";
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -195,6 +195,23 @@ let
|
|||
agentForgeBao.systemd.paths.forge-avatar-sync.pathConfig.PathChanged == tokenFile agentForgeBao
|
||||
&& builtins.elem "hive-agent-forge-token.service" agentForgeBao.systemd.services.forge-avatar-sync.after;
|
||||
}
|
||||
{
|
||||
# The poller waits for the token about ten minutes and then exits 0, which
|
||||
# `on-failure` does not restart; the path unit starts it when either file
|
||||
# it reads appears. Control: the avatar watcher beside it, in the same set.
|
||||
name = "a token that lands after the forge poller gave up starts it again";
|
||||
ok =
|
||||
let
|
||||
path = agentForgeBao.systemd.paths.hive-forge-notify;
|
||||
in
|
||||
path.pathConfig.PathChanged == [
|
||||
(tokenFile agentForgeBao)
|
||||
"/agents/${agentForgeBao.services.hyperhive.agent.user.name}/state/forge-token"
|
||||
]
|
||||
&& path.wantedBy == [ "multi-user.target" ]
|
||||
&& agentForgeBao.systemd.services ? hive-forge-notify
|
||||
&& agentForgeBao.systemd.paths ? forge-avatar-sync;
|
||||
}
|
||||
{
|
||||
# Both readers take the fetched token first and fall back to the state
|
||||
# file, which is still the only copy for an agent with no store identity.
|
||||
|
|
|
|||
|
|
@ -102,6 +102,21 @@ let
|
|||
name = "the fetch runs before the github poller starts";
|
||||
ok = builtins.elem "hive-github-notify.service" (fetchUnit agentGithubBao).before;
|
||||
}
|
||||
{
|
||||
# The poller waits for the token about ten minutes and then exits 0, which
|
||||
# `on-failure` does not restart; the path unit starts it when the token lands.
|
||||
name = "a token that lands after the github poller gave up starts it again";
|
||||
ok =
|
||||
let
|
||||
name = agentGithubBao.services.hyperhive.agent.user.name;
|
||||
path = agentGithubBao.systemd.paths.hive-github-notify;
|
||||
in
|
||||
path.pathConfig.PathChanged == "/agents/${name}/state/github-token"
|
||||
&& path.wantedBy == [ "multi-user.target" ]
|
||||
&& agentGithubBao.systemd.services ? hive-github-notify
|
||||
&& !(agentGithubOff.systemd.paths ? hive-github-notify)
|
||||
&& !(agentGithubOff.systemd.services ? hive-github-notify);
|
||||
}
|
||||
{
|
||||
name = "the fetch re-runs every two minutes";
|
||||
ok = agentGithubBao.systemd.timers.hive-agent-github-token.timerConfig.OnUnitInactiveSec == "2min";
|
||||
|
|
|
|||
Loading…
Reference in a new issue