agent: derive the forge git credential scope instead of hand-writing it
Nothing in the tree rendered a `[credential "<forge>"]` entry for an
agent, so every agent's `~/.gitconfig` accumulated one by hand, per
generation of forge address. Append-only, none ever removed, and after
the domain move the live one absent entirely:
[credential "http://forge.<old-hive>"]
[credential "http://localhost:3000"]
The absence of a writer is the defect. A value interpolated at eval time
follows a rename; a value captured into a mutable home file does not.
hive-c0re's own gitconfig already derives its scope from
`swarm.forge.domain` and moved correctly for exactly that reason.
What made it expensive to diagnose is that it does not present as a
credential problem. `git fetch` against a stale remote still succeeds --
the old name redirects and a public read needs no auth -- so the break
surfaces only at the first authenticated push, long after the move, as
`could not read Username for '<new host>'`. That names a host the agent
was never configured for, which reads like DNS or TLS.
Same class as the CI runner keeping its registered address, one tier
down.
The shape is `github.nix`'s, unchanged: a small credential helper that
reads the token from the agent's state file at invocation, with the
token PATH baked in rather than the value, because claude's Bash tool
runs in a minimal env that never sources /etc/set-environment.
`environment.etc."gitconfig"` is already bound by github.nix; the two
merge rather than collide because the option is `lines`. Verified by
eval with both modules defining it before this was written -- a silent
last-wins there would drop one integration's credentials and look
exactly like this bug again.
This commit is contained in:
parent
c2b6bbd23b
commit
399c6f7422
1 changed files with 55 additions and 1 deletions
|
|
@ -17,6 +17,26 @@ let
|
|||
iconPng = pkgs.runCommand "hive-agent-icon.png" { nativeBuildInputs = [ pkgs.librsvg ]; } ''
|
||||
rsvg-convert -f png -w 512 -h 512 ${config.hyperhive.icon} -o $out
|
||||
'';
|
||||
|
||||
# git credential helper for the hive forge --- the exact shape
|
||||
# `./github.nix` uses for github.com, for the same two reasons: the token
|
||||
# is read from the agent's state file AT INVOCATION (so a re-issued token
|
||||
# takes effect with no rebuild), and the token PATH is baked in at build
|
||||
# time rather than read from the environment, because claude's Bash tool
|
||||
# runs `bash -c` in a minimal env that never sources `/etc/set-environment`.
|
||||
#
|
||||
# The alternative --- a token spliced into `remote.origin.url` --- is worse
|
||||
# than it looks: any command that prints a remote (`git remote -v`, a push
|
||||
# failure) writes the secret into `harness/bash-tasks/*.{out,err}`, which is
|
||||
# bind-mounted rw into the agent and never swept.
|
||||
gitCredHelper = pkgs.writeShellScriptBin "git-credential-hive-forge" ''
|
||||
# git credential-helper protocol: only `get` needs an answer.
|
||||
[ "''${1:-}" = "get" ] || exit 0
|
||||
TOKEN_FILE="/agents/${userName}/state/forge-token"
|
||||
[ -r "$TOKEN_FILE" ] || exit 0
|
||||
printf 'username=%s\n' ${lib.escapeShellArg userName}
|
||||
printf 'password=%s\n' "$(cat "$TOKEN_FILE")"
|
||||
'';
|
||||
in
|
||||
{
|
||||
options.hyperhive.forge.url = lib.mkOption {
|
||||
|
|
@ -76,7 +96,41 @@ in
|
|||
# (view, pr, issue, comment, assign, close, labels, branches, etc.).
|
||||
# The per-bin split package — narrow closure, no hivectl/wireguard.
|
||||
config.hyperhive.packages.hive-forge
|
||||
];
|
||||
]
|
||||
++ lib.optional (config.hyperhive.forge.url != null) gitCredHelper;
|
||||
|
||||
# Wire the forge credential helper for `git push`, scoped to the forge
|
||||
# this agent is configured for.
|
||||
#
|
||||
# ⚠️ THE SCOPE IS DERIVED, AND THAT IS THE ENTIRE POINT. Before this
|
||||
# existed nothing rendered it, so each agent's `~/.gitconfig` accumulated a
|
||||
# hand-written `[credential "<url>"]` per generation of forge address —
|
||||
# append-only, none removed, and after a domain move the live one absent.
|
||||
# A value interpolated at eval time follows a rename; a value captured into
|
||||
# a mutable home file does not.
|
||||
#
|
||||
# The failure it caused is worth naming because it does not look like a
|
||||
# credential problem: `git fetch` against a stale remote still SUCCEEDS
|
||||
# (the old name redirects, and a public read needs no auth), so the break
|
||||
# surfaces only at the first authenticated push, as
|
||||
# `could not read Username for '<new host>'` — naming a host the agent was
|
||||
# never configured for, which reads like DNS or TLS.
|
||||
#
|
||||
# Trailing slash stripped: git matches a credential section by
|
||||
# scheme+host+port, and `http://host/` is not that.
|
||||
#
|
||||
# Nested-path binding, matching `./github.nix` — and the two MERGE rather
|
||||
# than collide, because `environment.etc.<name>.text` is `lines`. Verified
|
||||
# by eval with both modules defining it, not assumed: a silent last-wins
|
||||
# here would drop one integration's credentials and look exactly like this
|
||||
# bug again.
|
||||
environment.etc."gitconfig" = lib.mkIf (config.hyperhive.forge.url != null) {
|
||||
text = ''
|
||||
[credential "${lib.removeSuffix "/" config.hyperhive.forge.url}"]
|
||||
helper = git-credential-hive-forge
|
||||
username = ${userName}
|
||||
'';
|
||||
};
|
||||
|
||||
# Forge notification poller — a long-running sibling of
|
||||
# `hive-bash-daemon` / `hive-matrix-daemon`. Polls the agent's unread
|
||||
|
|
|
|||
Loading…
Reference in a new issue