Release P — v0.51.40 — 4-PR contributor batch (quota subprocess hardening + env-lock prewarm + cron one-shot warning + Xiaomi env key)#2037
Conversation
# Conflicts: # CHANGELOG.md
Opus Advisor review — stage-334 (Release P)
Verification (Opus pulled actual files at
|
nesquena
left a comment
There was a problem hiding this comment.
Review — end-to-end ✅ (clean APPROVE, no fix pushed)
What this ships
Release P (v0.51.40) — 4-PR contributor batch, mixed surfaces:
- #2030 (@Michaelyklam) — Hardens the account-usage quota probe subprocess path. Adds module-level
BoundedSemaphore(_MAX_CONCURRENT_ACCOUNT_USAGE_PROBES=2),stdin=subprocess.DEVNULL, POSIXpreexec_fnthat callsprctl(PR_SET_PDEATHSIG, SIGTERM)viactypes.CDLL(None), and a child-side_ACCOUNT_USAGE_PARENT_DEATHSIG_BOOTSTRAPprepended to thepython -cpayload. Slice 1 of N for #1912. - #2032 (@Michaelyklam) — Moves
tools.skills_tool/tools.skill_manager_toolimports out of_ENV_LOCK. New_prewarm_skill_tool_modules()runs before the lock; in-lock path usessys.modules.get(...)plus existingHERMES_HOME/SKILLS_DIRattribute patching. Closes #2024. - #2033 (@franksong2702) — Surfaces one-shot cron semantics in the Scheduled Jobs form. New
_cronScheduleKindForInput()classifier, hidden warning div, liveinput/changelisteners, 8 locale strings + 1 CSS class. Refs #2031. - #2034 (@franksong2702) — Closes the Xiaomi
XIAOMI_API_KEYenv-detection gap (issue #2025). Updatesget_available_models()configured-badge env list + detector,_PROVIDER_ENV_VARmap, onboarding metadata. 6 existing test scrub-lists extended.
~769 insertions / 32 deletions across 20 files. CI green on 3.11/3.12/3.13.
Traced against upstream hermes-agent
Pulled a fresh tarball (/tmp/hermes-agent-fresh). Verified the load-bearing pieces:
- Xiaomi —
hermes_cli/auth.py:377-383declaresProviderConfig(id="xiaomi", api_key_env_vars=("XIAOMI_API_KEY",), inference_base_url="https://api.xiaomimimo.com/v1").hermes_cli/config.py:1799listsXIAOMI_API_KEYin the env-var help registry.agent/model_metadata.py:361-362maps bothapi.xiaomimimo.comandxiaomimimo.comtoxiaomi. The webui's new env-var is what the CLI already expects — perfect round-trip. No webui-internal convention introduced. - Skill tools —
agent/skill_commands.py:60,147,251andagent/display.py:314importtools.skills_tool/tools.skill_manager_toolfrom within the agent. The webui's prewarm just primessys.modulesfor the same modules; nothing about the agent's expectations changes.
End-to-end trace
#2030 (api/providers.py)
- Semaphore declaration at
api/providers.py:49(_MAX_CONCURRENT_ACCOUNT_USAGE_PROBES = 2), lazy bootstrap atapi/providers.py:88-94. _account_usage_preexec_fn()atapi/providers.py:104-109— bareexcept Exception: passmakes it safe on platforms withoutprctl(Alpine/musl works viactypes.CDLL(None)which loads main-program symbols).- Subprocess kwargs assembled at
api/providers.py:609-621.stdin=subprocess.DEVNULLis set unconditionally;preexec_fnonly attached whenhasattr(os, "fork")is True — Windows safely skipped. - Subprocess invocation at
api/providers.py:622-630. The bootstrap_ACCOUNT_USAGE_PARENT_DEATHSIG_BOOTSTRAPis concatenated to the existing_ACCOUNT_USAGE_SUBPROCESS_CODEstring and passed as the single-cpayload. Bootstrap is a frozen string literal — no user input flows into the-carg. - Semaphore acquired around the entire
subprocess.run()atapi/providers.py:664-670(inside_fetch_account_usage_with_profile_context).
#2032 (api/streaming.py)
- Helper at
api/streaming.py:44-63—__import__(name)with hardcoded module names, swallowsImportErroronly. - Called at
api/streaming.py:2270— before the firstwith _ENV_LOCK:at line 2275. AST regression test enforces this ordering invariant. - In-lock body at
api/streaming.py:2294-2310— replaces previousimport tools.skills_tool as _sk(held the lock during first-import) with_sys.modules.get('tools.skills_tool')then guarded attribute patching. O(1) dict lookup, no import machinery under the lock. - Teardown lock at
api/streaming.py:3753is env-restore only — no imports there either, as the AST test confirms.
#2033 (static/panels.js)
- Classifier at
static/panels.js:234-244. Branch order: trim → empty →everyprefix →@prefix → 5-field cron → ISO date → bare duration → fallthrough. _syncCronScheduleWarning()atstatic/panels.js:247-252. Idempotent: togglesdisplay: '' / 'none'oncronFormScheduleOnceWarning.- Hidden warning div at
static/panels.js:745— text isesc(t('cron_schedule_once_warning') || "Duration forms like '30m' run once and are removed after running. Use 'every 30m' to keep a recurring job."). XSS-safe (esc()on the fallback literal). - Listeners attached at
static/panels.js:785-787inside_renderCronForm. Input element is recreated on each render, so no listener accumulation.
#2034 (api/config.py, api/onboarding.py, api/providers.py)
_build_configured_model_badges()inner function (nested inget_available_models()nearapi/config.py:2514) —XIAOMI_API_KEYadded to env-tracked list atapi/config.py:2763, detector atapi/config.py:2799-2800._PROVIDER_ENV_VARmap atapi/providers.py:177adds"xiaomi": "XIAOMI_API_KEY". Disjoint from #2030's hunks (49/85/104/606-670).- Onboarding setup at
api/onboarding.py:142-150—label="Xiaomi MiMo",env_var="XIAOMI_API_KEY",default_model="mimo-v2.5-pro",default_base_url="https://api.xiaomimimo.com/v1"— matches the CLI'sinference_base_urlexactly.
Other audit — things that are correct already
Security
_ACCOUNT_USAGE_PARENT_DEATHSIG_BOOTSTRAPis a frozen string literal — no user data is interpolated into the-cargument. Concatenation with_ACCOUNT_USAGE_SUBPROCESS_CODEhappens at module level.api_keycontinues to be passed asargv[2], same as before — not a regression.- New
_account_usage_preexec_fn()swallows all exceptions silently. On macOS / non-Linux POSIX,prctlis missing → no-op → safe. _prewarm_skill_tool_modules()only catchesImportError(not bare Exception). A SyntaxError intools.skills_toolwould now surface before the lock, which is strictly better than the pre-fix behaviour._cronScheduleKindForInputconsumes a user-controlled string. All checks are string-shape predicates; noeval, noinnerHTML, no template expansion of input. Warning text isesc()'d.- Xiaomi env-var is pure additive — adds a new fallback to a frozen allowlist; can't introduce a routing-shape regression.
Thread safety
_get_account_usage_probe_semaphore()lazily creates the singleton. Race on first creation could in theory create two semaphores; benign because the second_account_usage_probe_semaphorewould simply replace the first and either object has the right cap. Worst case: a momentary cap of 3 instead of 2 during startup. Acceptable._prewarm_skill_tool_modules()calls__import__— Python's import lock (_imp.acquire_lock()) serialises concurrent first-imports of the same module; safe._syncCronScheduleWarning()runs on the UI thread only.
Test isolation
- 6 tests already scrubbing provider envs add
XIAOMI_API_KEYto their scrub-lists. Tests now reproducible regardless of host env.
Behavioural harnesses
#2030 semaphore — spawned 4 threads contending for _get_account_usage_probe_semaphore(). Max concurrent holders observed: 2. _account_usage_preexec_fn() invoked directly returned silently. hasattr(os, 'fork') confirmed True on host.
#2033 cron classifier — 19 inputs against the extracted JS function:
✓ "30m" → once ✓ "every 30m" → interval
✓ "2h" → once ✓ "Every 2h" → interval
✓ "1 day" → once ✓ "every 1 hour" → interval
✓ "45 mins" → once ✓ "EVERY 30m" → interval
✓ "2026-05-11" → once ✓ "0 9 * * *" → cron
✓ "2026-05-11T08:00" → once ✓ "*/15 * * * *" → cron
✓ "@daily" → cron
✓ "" → "" ✓ "@hourly" → cron
✓ "gibberish" → ""
✓ "100" → "" (bare number — no false-positive warning)
✓ " " → ""
ALL PASS (19/19)
Edge-case matrix
| Scenario | Expected | Actual |
|---|---|---|
| 4 concurrent quota probes, cap=2 | only 2 in-flight | ✅ verified |
_account_usage_preexec_fn() on macOS |
silent no-op | ✅ verified |
_account_usage_preexec_fn() on Linux |
calls prctl, swallow on ABI mismatch | ✅ code review |
_prewarm_skill_tool_modules() when modules missing |
silently catches ImportError |
✅ confirmed |
_ENV_LOCK body re-entered after fix |
no import tools.skills_tool inside |
✅ AST test |
Stale listener after _renderCronForm rerender |
input element recreated, no leak | ✅ DOM contract |
cron_schedule_once_warning XSS |
esc() on fallback literal |
✅ source |
XIAOMI_API_KEY round-trip CLI ↔ webui |
CLI expects same var | ✅ hermes_cli/auth.py:382 |
XIAOMI_API_KEY not detected when env unset |
_provider_has_key('xiaomi') False |
✅ test #2025 |
5-field cron * with comma list 0,15,30,45 * * * * |
→ 'cron' | ✅ harness |
@daily / @hourly |
→ 'cron' | ✅ harness |
Tests
- PR-targeted: 28/28 pass (
test_issue2024_env_lock_skill_imports.py,test_issue2025_xiaomi_env_key.py,test_issue2031_cron_once_visibility.py, new quota tests intest_provider_quota_status.py). - Full suite (local Python 3.14): 4991 passed, 59 skipped, 3 xpassed. 5 failures are pre-existing baseline failures in
test_docker_env_readonly_vars.py::TestStartShReadonlyEnvFilterBehavioral(macOS bash 3.2 readonly-filter behaviour). PR does not touchdocker_init.bash,docker_start.sh, ortests/test_docker_env_readonly_vars.py— confirmed bygit diff master..stage-334. - PR's CI: 5100 passed / 8 skipped / 1 xfailed / 2 xpassed in 151.94s on Python 3.11 (per PR body); 3.11 / 3.12 / 3.13 all green.
Minor observations (non-blocking)
- Cap=2 vs Settings poll fan-out. Opus advisor already flagged: with 5-6 OAuth-backed providers polling concurrently, the 3rd+ thread blocks up to 35 s waiting on a slot. Comment at
api/providers.py:43-47is honest about this. Follow-up: raise cap or coalesce in-flight probes per provider. Tracked in #1912. _account_usage_preexec_fnlacksargtypes.libc.prctl(1, signal.SIGTERM)is called without settingprctl.argtypes. On a Linux that ever changes the prctl ABI, the bareexceptswallows it → orphan probes (same as pre-PR behaviour, not worse). Best-effort by design.- Lock-body
from pathlib import Path as _P/import sys as _sys. These remain inside_ENV_LOCK, but both are stdlib and warm insys.moduleslong before — first-time cost is zero. Not worth moving. - #2033 has only static + extracted-function coverage — no JSDOM/Playwright
display:none ↔ display:''toggle check. Behavioural risk is small for a hint div; fine as a follow-up.
Recommendation
Approved. Code is correct, well-isolated, cross-tool-consistent. The Opus advisor's SHIP-WITH-CAVEATS verdict matches what I traced: the cap=2 UX question is a follow-up, not a blocker.
✅ Parked at approval — ready for the release agent's merge/tag pipeline.
Release P — v0.51.40 — 4-PR contributor batch
Theme: Quota subprocess hardening + env-lock prewarm + cron one-shot warning + Xiaomi env key. Mixed-author, mixed-surface batch.
PRs included
stdin=DEVNULL, POSIXpreexec_fnwiresPR_SET_PDEATHSIG._ENV_LOCK(closes Move tools.skills_tool / tools.skill_manager_tool imports out of _ENV_LOCK in api/streaming.py #2024);_prewarm_skill_tool_modules()runs before lock acquisition, in-lock path usessys.modules.get(...).30m/2h/ISO-date forms, surfaces inline warning towardevery 30mrecurring forms.XIAOMI_API_KEYenv-detection gap (issue Add XIAOMI_API_KEY env-var fallback for Xiaomi MiMo provider #2025); WebUI now treats Xiaomi like other API-key providers inget_available_models(), Settings/api/providers, and onboarding metadata.Verification
HERMES_HOMEisolation)node --checkclean on all touched .js filesFile collisions (stage merge)
api/providers.pytouched by fix: harden quota probe subprocess handling #2030 (+110/-7) and Fix Xiaomi API key env detection #2034 (+1) — verified disjoint hunks (fix: harden quota probe subprocess handling #2030 at lines 49/67/85/621/664, Fix Xiaomi API key env detection #2034 at line 105 in_PROVIDER_ENV_VARmap)CHANGELOG.mdUnreleased section conflict between Clarify one-shot cron schedule behavior #2033 and Fix Xiaomi API key env detection #2034 — resolved by keeping both bullets, then promoted to v0.51.40 release entryOpus advisor verdict
SHIP-WITH-CAVEATS — code correct, well-isolated, tested. Caveats are follow-ups, not blockers. Full review posted as a separate comment.
Tests
5082 → 5100 (+18 net new across 4 new test files).
Holds untouched
7 PRs with
holdlabel left untouched per explicit non-hold scope: #1418, #1721, #1884, #1924, #1970, #1975, #1997.cc @nesquena — requesting independent review per self-built work policy.