hermes: recover interrupted provider CLI installs

This commit is contained in:
jenkins 2026-09-13 14:11:40 -05:00
parent 0192ef8ee8
commit 364d1b3503
2 changed files with 79 additions and 1 deletions

View File

@ -475,7 +475,21 @@ spec:
# which self-heals after the pod has run on the other architecture.
case "$(uname -m)" in x86_64) nodearch=x64 ;; aarch64) nodearch=arm64 ;; *) nodearch=unknown ;; esac
codex_native="${tools}/lib/node_modules/@openai/codex-linux-${nodearch}"
if [ ! -f "${tools}/.cli-versions-0.154.0-2.1.226-${arch}" ] || [ ! -d "${codex_native}" ]; then
codex_version="$("${tools}/bin/codex" --version 2>/dev/null || true)"
claude_version="$("${tools}/bin/claude" --version 2>/dev/null || true)"
case "${codex_version}" in *0.154.0*) codex_ready=1 ;; *) codex_ready=0 ;; esac
case "${claude_version}" in *2.1.226*) claude_ready=1 ;; *) claude_ready=0 ;; esac
if [ ! -f "${tools}/.cli-versions-0.154.0-2.1.226-${arch}" ] || [ ! -d "${codex_native}" ] \
|| [ "${codex_ready}" != "1" ] || [ "${claude_ready}" != "1" ]; then
# Interrupted npm global installs leave only these private
# staging directories; never follow or remove other entries.
for scope in "${tools}/lib/node_modules/@anthropic-ai" "${tools}/lib/node_modules/@openai"; do
[ -d "${scope}" ] && [ ! -L "${scope}" ] || continue
case "${scope}" in
*/@anthropic-ai) find "${scope}" -mindepth 1 -maxdepth 1 -type d -name '.claude-code-*' -exec rm -rf -- {} + ;;
*/@openai) find "${scope}" -mindepth 1 -maxdepth 1 -type d -name '.codex-*' -exec rm -rf -- {} + ;;
esac
done
npm install --global --omit=dev --no-audit --no-fund --prefix "${tools}" \
@openai/codex@0.154.0 \
@anthropic-ai/claude-code@2.1.226

View File

@ -2,6 +2,9 @@
from __future__ import annotations
import os
import subprocess
from testing.tests.test_hermes_cli_support import (
HERMES,
Path,
@ -61,6 +64,13 @@ def test_owner_agent_installs_the_pinned_operator_toolchain():
)
assert "/bin/sh /opt/coordinator/install_agent_tools.sh" in installer["command"][2]
assert any(mount["name"] == "coordinator" for mount in installer["volumeMounts"])
install = installer["command"][2]
assert 'codex_version="$("${tools}/bin/codex" --version' in install
assert 'claude_version="$("${tools}/bin/claude" --version' in install
assert "@anthropic-ai" in install and "-name '.claude-code-*'" in install
assert "@openai" in install and "-name '.codex-*'" in install
assert "-mindepth 1 -maxdepth 1 -type d" in install
assert "[ ! -L \"${scope}\" ]" in install
init_config = next(
item
for item in deployment["spec"]["template"]["spec"]["initContainers"]
@ -72,6 +82,60 @@ def test_owner_agent_installs_the_pinned_operator_toolchain():
assert 'chmod 0644 "${profile_file}"' in init_command
def test_npm_reinstall_removes_only_known_interrupted_staging_directories(tmp_path):
"""A stale npm rename target is cleared before the pinned reinstall only."""
installer = next(
item
for item in _agent_deployment()["spec"]["template"]["spec"]["initContainers"]
if item["name"] == "install-agent-tools"
)
tools = tmp_path / "tools"
bin_dir = tools / "bin"
bin_dir.mkdir(parents=True)
for name, output in {
"ttyd": "ttyd 1.7.7\n",
"kubectl": '{"gitVersion":"v1.33.3"}\n',
"codex": "codex 0.154.0\n",
"claude": "2.1.226\n",
}.items():
path = bin_dir / name
path.write_text(f"#!/bin/sh\nprintf '%s' '{output}'\n", encoding="utf-8")
path.chmod(0o755)
for scope, stale in (("@anthropic-ai", ".claude-code-aborted"), ("@openai", ".codex-aborted")):
path = tools / "lib/node_modules" / scope / stale
path.mkdir(parents=True)
(path / "partial").write_text("incomplete", encoding="utf-8")
preserved = tools / "lib/node_modules/@anthropic-ai/keep-me"
preserved.mkdir(parents=True)
(preserved / "data").write_text("preserve", encoding="utf-8")
fake_npm = tmp_path / "fake-bin/npm"
fake_npm.parent.mkdir()
fake_npm.write_text(
"#!/bin/sh\n"
"test ! -e \"$HERMES_TEST_TOOLS/lib/node_modules/@anthropic-ai/.claude-code-aborted\"\n"
"test ! -e \"$HERMES_TEST_TOOLS/lib/node_modules/@openai/.codex-aborted\"\n",
encoding="utf-8",
)
fake_npm.chmod(0o755)
command = installer["command"][2]
command = command.replace("tools=/opt/data/tools", f"tools={tools}")
command = command.replace("/bin/sh /opt/coordinator/install_agent_tools.sh", ":")
command = command.replace('HOME=/opt/data/home git config --global user.name "Hermes Agent"', ":")
command = command.replace('HOME=/opt/data/home git config --global user.email "hermes@bstein.dev"', ":")
result = subprocess.run(
["sh", "-c", command],
text=True,
capture_output=True,
env={
**os.environ,
"PATH": f"{fake_npm.parent}:{os.environ['PATH']}",
"HERMES_TEST_TOOLS": str(tools),
},
)
assert result.returncode == 0, result.stderr
assert (preserved / "data").read_text(encoding="utf-8") == "preserve"
def test_owner_agent_uses_the_canonical_hostname():
paths = [
HERMES / "agent-configmap.yaml",