Summary
Mirror to Git Forges has failed on every recent run of main, and 3 of
its 7 forge jobs are broken for three different reasons. One of them is a
silent empty-variable expansion, not an auth problem — which matters, because
the three look identical from the workflow list.
This is pre-existing and unrelated to #842: red at 450734e, 4fa2e39,
c18608e, 1b2e66e5 and 0aa89724.
Measured — run 35772116797 @ 450734ea021167b2d9c2a85e9976bc6708deb379
| Job |
Result |
mirror-gitlab |
✅ success |
mirror-codeberg |
✅ success |
mirror-radicle |
✅ success |
mirror-sourcehut |
✅ success |
mirror-gitea |
❌ empty host variable |
mirror-disroot |
❌ Permission denied (publickey) |
mirror-bitbucket |
❌ Could not read from remote repository |
Fault 1 — mirror-gitea: the host expands to the empty string
Step 4 Mirror to Gitea runs:
ssh-keyscan -t ed25519 >> ~/.ssh/known_hosts
^^ two spaces -- the host argument is MISSING
ssh-keyscan then prints its usage line ([-t type] [host | addrlist namelist]) and exits non-zero. Compare the sibling jobs, where the host is
present and correct:
ssh-keyscan -t ed25519 git.disroot.org >> ~/.ssh/known_hosts
ssh-keyscan -t ed25519 bitbucket.org >> ~/.ssh/known_hosts
So the secret or variable supplying the Gitea host does not exist, and
GitHub expanded it to empty rather than failing. This is not an authentication
fault and will not be fixed by rotating a key — the job never learned which
host to contact.
⚠ A nonexistent secret resolves EMPTY and silently. The job name says
"Gitea", the log says ssh-keyscan failed, and neither says "your variable is
undefined". Any ${{ secrets.X }} / ${{ vars.X }} used as a command
argument needs an explicit non-empty assertion before use.
Faults 2 and 3 — mirror-disroot and mirror-bitbucket: the key is not authorized
disroot: git@git.disroot.org: Permission denied (publickey).
bitbucket: fatal: Could not read from remote repository.
Host resolution is fine (both keyscans carry a real hostname). The deploy key
presented is not accepted by the remote. Either the public key is not
registered on that forge, the wrong private key is in the secret, or the remote
repository does not exist under the configured path.
These two need an owner action (registering a public key on those forges);
fault 1 does not — it is a workflow/config fix.
Acceptance criteria
- Every host-valued secret/var is asserted non-empty before use. Add a
guard step per forge that fails with a named message
(GITEA_HOST is unset or empty) rather than passing an empty argument to
ssh-keyscan. Applies to all 7 jobs, not just Gitea.
- Mutant: blank one forge's host variable deliberately; the job must fail
at the assertion naming that variable, not at ssh-keyscan usage.
Restore it; green returns.
- The workflow reports a denominator:
mirrored N of 7 forges. A run where
some forges are deliberately unconfigured must state which were skipped and
why — a skip must never read as a mirror.
- Decide per forge: configure or remove. A permanently-failing mirror job
trains everyone to ignore a red workflow. If Disroot/Bitbucket/Gitea are not
wanted, delete those jobs; if they are, register the keys and prove a push
lands by checking the mirrored ref's SHA equals main.
- Verify by state, not exit code: after a green run, fetch each mirror's
default-branch SHA and assert it equals hypatia main. A mirror job that
pushes nothing can still exit 0.
Context
Found while accounting for every red on main after #842 (450734e). The
other three reds are tracked: #843 (E2E), #844 (Security), #520 (Governance
baseline). This was the only untracked one.
Summary
Mirror to Git Forgeshas failed on every recent run ofmain, and 3 ofits 7 forge jobs are broken for three different reasons. One of them is a
silent empty-variable expansion, not an auth problem — which matters, because
the three look identical from the workflow list.
This is pre-existing and unrelated to #842: red at
450734e,4fa2e39,c18608e,1b2e66e5and0aa89724.Measured — run
35772116797@450734ea021167b2d9c2a85e9976bc6708deb379mirror-gitlabmirror-codebergmirror-radiclemirror-sourcehutmirror-giteamirror-disrootPermission denied (publickey)mirror-bitbucketCould not read from remote repositoryFault 1 —
mirror-gitea: the host expands to the empty stringStep 4
Mirror to Gitearuns:ssh-keyscanthen prints its usage line ([-t type] [host | addrlist namelist]) and exits non-zero. Compare the sibling jobs, where the host ispresent and correct:
So the secret or variable supplying the Gitea host does not exist, and
GitHub expanded it to empty rather than failing. This is not an authentication
fault and will not be fixed by rotating a key — the job never learned which
host to contact.
⚠ A nonexistent secret resolves EMPTY and silently. The job name says
"Gitea", the log says
ssh-keyscanfailed, and neither says "your variable isundefined". Any
${{ secrets.X }}/${{ vars.X }}used as a commandargument needs an explicit non-empty assertion before use.
Faults 2 and 3 —
mirror-disrootandmirror-bitbucket: the key is not authorizedHost resolution is fine (both keyscans carry a real hostname). The deploy key
presented is not accepted by the remote. Either the public key is not
registered on that forge, the wrong private key is in the secret, or the remote
repository does not exist under the configured path.
These two need an owner action (registering a public key on those forges);
fault 1 does not — it is a workflow/config fix.
Acceptance criteria
guard step per forge that fails with a named message
(
GITEA_HOST is unset or empty) rather than passing an empty argument tossh-keyscan. Applies to all 7 jobs, not just Gitea.at the assertion naming that variable, not at
ssh-keyscanusage.Restore it; green returns.
mirrored N of 7 forges. A run wheresome forges are deliberately unconfigured must state which were skipped and
why — a skip must never read as a mirror.
trains everyone to ignore a red workflow. If Disroot/Bitbucket/Gitea are not
wanted, delete those jobs; if they are, register the keys and prove a push
lands by checking the mirrored ref's SHA equals
main.default-branch SHA and assert it equals hypatia
main. A mirror job thatpushes nothing can still exit 0.
Context
Found while accounting for every red on
mainafter #842 (450734e). Theother three reds are tracked: #843 (E2E), #844 (Security), #520 (Governance
baseline). This was the only untracked one.