Repository navigation
CI: Make the internal LLMEvalKit install diagnosable, and skip it when GHES is unreachable - #1999
Merged
Merged
Conversation
A failure in `.github/actions/install-internal-pip` was almost unreadable.
GitHub masks the step's output because it carries a secret, and the step ran
under `-e -o pipefail`, so any failing command aborted it with no output at all:
##[error]Process completed with exit code 1
Five distinct causes -- absent secret, malformed secret, unreachable GHES,
rejected credential, broken package -- all looked exactly alike. This is not
hypothetical: it is how CI is failing right now. `unittests`, `eager`,
`performance` and all ten `preparation` shards die in this step, on main as
well as on every open PR, and the only way to tell that the cause is network
reachability rather than the code under test is to notice that the
unconditional `echo "Installing from URL: ..."` never printed, so execution
must have stopped in the three commands before it.
Each precondition is now checked separately and fails with its own `::error`
annotation, which shows in the job summary without opening the log:
- Malformed SSH key -- `ssh-keygen` cannot load the secret
- GHES unreachable -- `ssh-keyscan -T 15` got no host key
- SSH key rejected -- the host refused the deploy key
- Internal install failed -- host and credential fine, pip failed
- pydantic pin failed -- the package installed, the pin did not
Each message says which external dependency broke and where to look, and the
two infrastructure cases say outright that the pull request is not at fault.
Implementation notes:
`set -e` is dropped deliberately. Every stage now reports before it exits, so
the step can no longer abort without an explanation; `-o pipefail` is kept.
The auth probe matches ssh's *message*, not its exit status: a successful
`ssh -T git@github.com` exits 1 and prints "Hi <user>! You've successfully
authenticated". Keying on the status would fail CI on a perfectly good key.
It also passes `-i ~/.ssh/id_ed25519 -o IdentitiesOnly=yes`, so an agent key
cannot answer in place of the deploy key and mask a rejection.
`ssh-keyscan` exits non-zero but silently on an unroutable host, so each
diagnostic echo is guarded with `[ -n ... ]` rather than printing an empty
line, and the success-path echo is followed by `true` so an empty greeting
cannot leave a passing step with a non-zero status.
Inputs now reach the script through `env:` instead of being interpolated into
the shell body with `${{ }}`, which also removes a script-injection surface.
The "malformed key" message is worded to avoid reading as `key: <value>`,
which `detect-secrets` flags as a Secret Keyword. Reworded rather than marked
`pragma: allowlist secret`, so the hook keeps working on this file.
No change to the action's inputs, its defaults, or its behaviour on success:
a working install prints the same progress and exits 0, and a run with no
secret still skips with a notice and passes. The key check prints only the
type and fingerprint, never anything derived from the key material.
All six paths were exercised locally against a real SSH endpoint with a
stubbed pip -- success, skip-on-no-secret, malformed key, unreachable host,
rejected key and pip failure -- each confirmed to give the intended annotation
and exit status. The GHES-specific paths cannot be reproduced outside the IBM
network, so `github.com` stood in for the host; what is verified is the
classification logic, not GHES itself.
This only improves the error message. It does not fix the outage: the install
will keep failing until `github.ibm.com` is reachable from GitHub-hosted
runners again, but it will now say so.
Signed-off-by: Yoav Katz <katz@il.ibm.com>
The key check logged `ssh-keygen -l` output on success, which named the
deploy key's fingerprint, bit size and comment:
Deploy key loaded: 256 SHA256:<...> gha-llmevalkit (ED25519)
A fingerprint is a hash of the *public* half -- `ssh-keygen -l` returns the
same value for a key and its `.pub`, and it can be recomputed from the public
blob alone -- so it is not a credential and cannot be used to authenticate.
But this repository is public and so are its CI logs, and nothing here needs
the key to appear in them, so it no longer does.
The validation itself is unchanged and still catches a truncated or
wrongly-wrapped secret before it surfaces as an opaque "Permission denied":
`ssh-keygen -y` still parses the key, its output is now discarded, and the
success line just says the key is well-formed. Verified that a good key is
still accepted and that both a malformed and a truncated key are still
detected.
What is lost is the ability to tell from the log *which* key is deployed, so
a wrong-but-valid key now looks the same as a revoked one: both reach the
`SSH key rejected` annotation. That is the intended trade.
No other output was key-derived. The remaining echoes carry ssh's own
messages ("Permission denied (publickey)", the authenticated greeting), the
host, the repo and the pip URL -- checked for fingerprints, comments and key
material, none present.
Signed-off-by: Yoav Katz <katz@il.ibm.com>
An unreachable `github.ibm.com` is now a warning and a skip, not a failure. A GHES outage is someone else's infrastructure, and failing the job on it blocks every pull request in the repository on something no pull request can fix. That is not hypothetical: on 2026-11-07 `github.ibm.com:22` became unroutable from GitHub-hosted runners and took down `unittests`, `eager` and all ten `preparation` shards -- on `main` and on every open PR at once, with each PR appearing to have broken fourteen jobs. This handles an unreachable host the way the action already handles a missing secret: say so, skip the install, let the jobs run. The tests that need the package already skip themselves when it is absent, via `requires_llmevalkit` in `tests/library/test_metrics.py` -- 15 guarded tests, and none unguarded -- so the suite stays green and simply covers less. The imports in `src/unitxt/metrics.py` are lazy and `_requirements_list` already raises a clear install error, so nothing else is affected. A malformed or rejected key still fails. Those are misconfigurations in this repository, not outages, and skipping them silently would hide a broken secret indefinitely. The distinction is the point: this skips what we cannot fix and still fails what we can. The trade is deliberate, and recorded in the workflows README because it is not self-evident from a green tick: a passing run no longer proves the `llmevalkit` metrics were exercised. A `GHES unreachable, install skipped` warning on the job, or skipped `Reflection*` tests in the output, is how to tell. One comment is worded to avoid reading as `key: <value>`, which `detect-secrets` flags as a Secret Keyword, for the same reason as the error message in the previous commit. Verified all five paths locally with a stubbed pip: unreachable host now exits 0 with the warning; malformed key and rejected key still exit 1 with their annotations; success still installs and exits 0; a run with no secret still skips and passes. Also confirmed that a guarded test with the package absent skips rather than fails, leaving the run successful. Signed-off-by: Yoav Katz <katz@il.ibm.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
.github/actions/install-internal-pipinstalls the internalMLT/LLMEvalKitpackage from IBM GHES over SSH. Two problems, both live right now:##[error]Process completed with exit code 1and nothing else.On 2026-11-07
github.ibm.com:22became unroutable from GitHub-hosted runners and took downunittests,eagerand all tenpreparationshards, onmainand on every open PR at once. PR #1997 looked like it had broken fourteen jobs; it had broken none.1. The failure is now legible
GitHub masks this step's output because it carries a secret, and it ran under
-e -o pipefail, so the first failing command aborted it silently. Five unrelated causes were indistinguishable. Diagnosing the outage required noticing that an unconditionalechonever printed — not a reasonable ask.Each precondition is now checked separately with its own annotation, visible in the job summary without opening a log:
Internal install skipped(notice)GHES unreachable, install skipped(warning)Malformed SSH keyssh-keygencannot load the secretSSH key rejectedInternal install failedpydantic pin failedConfirmed in CI on this branch — the annotations API returned
level: failure,title: GHES unreachable, and the-T 15bound worked (the step took 15.2s instead of hanging).2. An outage no longer fails the job
An unreachable host is now handled like a missing secret: warn, skip the install, let the jobs run.
This is safe because the test suite already has the mechanism —
requires_llmevalkitintests/library/test_metrics.pyskips those tests when the package is absent, and its comment already anticipates "CI installs only when repository secrets are available." 15 guarded tests, none unguarded. Imports insrc/unitxt/metrics.pyare lazy and_requirements_listraises a clear install error, so nothing else is affected. Verified that a guarded test with the package absent skips and leaves the run successful.A malformed or rejected key still fails, because those are misconfigurations in this repository rather than outages, and skipping them would hide a broken secret indefinitely. This skips what we cannot fix and still fails what we can.
The trade, stated plainly
A green run no longer proves the
llmevalkitmetrics were exercised. That is the cost of not blocking the repository on a third party, and it is recorded in.github/workflows/README.mdrather than left implicit behind a green tick. AGHES unreachable, install skippedwarning, or skippedReflection*tests, is how to tell.Points worth a reviewer's attention
set -eis dropped deliberately. Every stage reports before it exits, so the step can no longer abort without an explanation.-o pipefailis kept.The auth probe matches ssh's message, not its exit status. A successful
ssh -T git@github.comexits 1 and printsHi <user>! You've successfully authenticated— keying on exit status would fail CI on a good key. Verified in both directions. It also pins to the deploy key with-i/IdentitiesOnly=yes: without that, an agent-offered key answers instead and a rejection is masked. That was a real bug caught in testing.ssh-keyscanfails silently on an unroutable host (non-zero, no output), so diagnostic echoes are[ -n ... ]-guarded, and the success echo is followed bytrueso an empty greeting cannot leave a passing step non-zero.No fingerprint is logged. An earlier revision printed
ssh-keygen -loutput on success. A fingerprint hashes only the public half and is not a credential, but this repository and its logs are public, so nothing key-derived is emitted at all. The validation is unchanged and still catches malformed and truncated keys.Inputs reach the script via
env:instead of${{ }}interpolated into the shell body, which also removes a script-injection surface.Two messages are worded to avoid reading as
key: <value>, whichdetect-secretsflags. Reworded rather than markedpragma: allowlist secret, so the hook keeps working on this file.Compatibility
No change to the action's inputs or defaults. A working install behaves exactly as before. All four call sites use defaults only and are untouched.
Verification
All five paths exercised locally against a real SSH endpoint with a stubbed pip: success, no-secret skip, unreachable host (now exit 0 + warning), malformed/truncated key (exit 1), rejected key (exit 1), pip failure (exit 1). YAML parses,
bash -nclean, pre-commit clean.GHES-specific paths cannot be reproduced outside the IBM network, so
github.comstood in for the host: what is verified is the classification logic, not GHES itself.Still outstanding
This makes the outage survivable, not fixed. Someone with IBM GHES/network access still needs to restore egress to
github.ibm.com:22before those metrics are actually tested again.LLMEVALKIT_SSH_KEYis confirmed not a factor —ssh-keygenloads it fine, so the earlier suspicion about its age can be dropped.🤖 Generated with Claude Code