chore(macos-patch): resilient litellm install + idempotent OpenClaw guards

Inject pip --retries/--resume-retries into the cloned litellm install task,
tolerate empty version-probe stdout via default('{}', true), and guard the
OpenClaw download/extract patches so a second pass cannot append a duplicate
`when:` (invalid YAML). Ignore scripts/__pycache__.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Haitao Pan 2026-06-22 13:25:12 +08:00
parent 7fd48bbf74
commit 54df83dc9e
2 changed files with 57 additions and 18 deletions

1
.gitignore vendored
View File

@ -52,3 +52,4 @@ coverage/
/dashboard/.ai-workspace-build-commit
*.textClipping
scripts/__pycache__/

View File

@ -316,7 +316,43 @@ def main():
for o, n in owner_subs:
if o in text:
text = text.replace(o, n, 1)
# litellm[proxy] pulls large wheels (polars-runtime ~46MB, etc.) that
# frequently break mid-stream over slow/mirrored links with
# IncompleteRead, failing the whole deploy. Make the online install
# resilient: --retries reconnects and --resume-retries (pip >= 25.1,
# which the macOS python@3.13 venv already ships) continues a partial
# download instead of restarting it. Until the playbooks repo carries
# this in the role itself, the curl|bash clone path needs it injected.
pip_old = (
' executable: "{{ litellm_pip_executable }}"\n'
' state: present\n'
' environment:\n'
' PIP_CACHE_DIR: "{{ litellm_pip_cache_dir }}"\n'
' PIP_DEFAULT_TIMEOUT: "120"\n'
)
pip_new = (
' executable: "{{ litellm_pip_executable }}"\n'
' state: present\n'
' extra_args: "--retries 5 --resume-retries 5"\n'
' environment:\n'
' PIP_CACHE_DIR: "{{ litellm_pip_cache_dir }}"\n'
' PIP_DEFAULT_TIMEOUT: "180"\n'
)
if pip_old in text and pip_new not in text:
text = text.replace(pip_old, pip_new, 1)
# `default('{}')` does NOT replace an empty string (only an undefined
# value), so when the "Inspect installed LiteLLM dependency versions"
# task returns empty stdout (common on a re-run / partial venv),
# from_json('') raises and the set_fact fails with a confusing
# "args could not be converted to dict" error. Use default(..., true)
# so empty/falsy stdout falls back to '{}'.
text = text.replace(
"default('{}') | from_json",
"default('{}', true) | from_json",
)
path.write_text(text)
# provision-database.yml runs psql with become_user postgres, which has no
@ -577,9 +613,19 @@ def main():
" mode: \"0644\"\n"
" when: ansible_os_family != 'Darwin'"
)
if download_old in text:
# Idempotency: download_new contains download_old as a prefix, so a
# second pass over an already-patched tree would otherwise append a
# second `when:` line (duplicate mapping key -> invalid YAML). Only
# apply when the patched form is not already present.
if download_old in text and download_new not in text:
text = text.replace(download_old, download_new, 1)
# NOTE: this block must match the upstream Extract task verbatim,
# including the `creates:` line and the multi-item `notify:` list
# (`Run OpenClaw doctor` + `Restart openclaw`). If it drifts from
# upstream the substitution silently no-ops and the Darwin guard is
# never added, so the task tries to unarchive a tarball that is never
# downloaded on macOS and the OpenClaw step fails.
extract_old = (
"- name: Extract OpenClaw Multi-Session Plugins\n"
" ansible.builtin.unarchive:\n"
@ -589,23 +635,15 @@ def main():
" owner: \"{{ gateway_openclaw_service_user }}\"\n"
" group: \"{{ gateway_openclaw_service_group }}\"\n"
" mode: \"0755\"\n"
" creates: \"{{ gateway_openclaw_home }}/.openclaw/extensions/openclaw-multi-session-plugins\"\n"
" become: \"{{ ansible_os_family != 'Darwin' }}\"\n"
" notify: Restart openclaw"
" notify:\n"
" - Run OpenClaw doctor\n"
" - Restart openclaw"
)
extract_new = (
"- name: Extract OpenClaw Multi-Session Plugins\n"
" ansible.builtin.unarchive:\n"
" src: \"/tmp/openclaw-multi-session-plugins.tar.gz\"\n"
" dest: \"{{ gateway_openclaw_home }}/.openclaw/extensions\"\n"
" remote_src: true\n"
" owner: \"{{ gateway_openclaw_service_user }}\"\n"
" group: \"{{ gateway_openclaw_service_group }}\"\n"
" mode: \"0755\"\n"
" become: \"{{ ansible_os_family != 'Darwin' }}\"\n"
" notify: Restart openclaw\n"
" when: ansible_os_family != 'Darwin'"
)
if extract_old in text:
extract_new = extract_old + "\n when: ansible_os_family != 'Darwin'"
# Same idempotency guard as the download task above.
if extract_old in text and extract_new not in text:
text = text.replace(extract_old, extract_new, 1)
anchor = "- name: Ensure OpenClaw global plugin npm directory exists"