chore(release): stamp v0.50.293 — 3-PR batch + 2 Opus follow-ups absorbed

Constituent PRs:
  #1627 by @franksong2702 — show Hermes Agent version (closes #1606)
  #1629 by @nesquena-hermes — profile isolation trio (closes #1611, #1612, #1614)
  #1630 by @Michaelyklam — provider config cleanup regression test (#1597 follow-up)

Opus advisor SHIP verdict + 2 SHOULD-FIX absorbed in-release:
- load_projects() re-reads from disk inside lock to close migration startup race
- _detect_agent_version() uses --dirty for symmetry with _detect_webui_version()

4142 → 4180 tests passing.
This commit is contained in:
Hermes Agent committed 2026-05-04 16:33:57 +00:00
1 parent 838645fd50
commit f3e066b53c
5 files changed
+29 -10

No files matched your search

+12 -5
View File
@@ -14,18 +14,25 @@
### Tests
4142 → **4181 passing** (+39 regression tests across `tests/test_issue1611_session_profile_filtering.py` (11), `tests/test_issue1612_renamed_root_profile.py` (11), `tests/test_issue1614_project_profile_filtering.py` (11), `tests/test_provider_management.py::test_clean_provider_key_uses_late_bound_config_path` (1), and `tests/test_version_badge.py` agent-detect chain (~5)). 0 regressions. Full suite in ~120s.
4142 → **4180 passing** (+38 regression tests across `tests/test_issue1611_session_profile_filtering.py` (11), `tests/test_issue1612_renamed_root_profile.py` (11), `tests/test_issue1614_project_profile_filtering.py` (11), `tests/test_provider_management.py::test_clean_provider_key_uses_late_bound_config_path` (1), and `tests/test_version_badge.py` agent-detect chain (~5)). 0 regressions. Full suite in ~120s.
### Pre-release verification
- Self-built fix (nesquena-hermes), Opus advisor pre-merge pass with 2 SHOULD-FIX absorbed in-PR (see Opus-applied fixes below); independent review APPROVED by nesquena pending.
- **Opus advisor on full stage-293 diff: SHIP verdict.** Two SHOULD-FIX items absorbed in-release per <20-LOC defensive policy: (a) `api/models.py:load_projects()` re-reads from disk inside `_PROJECTS_MIGRATION_LOCK` when `_projects_migrated` is found True post-wait — closes a startup-window staleness race where a thread that read pre-migration could return stale untagged rows after a peer migrated and wrote disk; (b) `_detect_agent_version()` now uses `git describe --tags --always --dirty` for symmetry with `_detect_webui_version()`. One non-blocking client-side filter cross-alias edge case deferred as follow-up issue.
- Self-built fix (#1629, nesquena-hermes), independent review **APPROVED by nesquena** with comprehensive end-to-end trace, cross-tool verification against fresh agent tarball, security audit, race/state analysis, and 13-row edge-case matrix.
- 31 dedicated regression tests for #1611/#1612/#1614 invariants. Source-string assertions pin the active-profile guards on `/api/projects/{rename,delete}` and `/api/session/move`.
- `_is_root_profile` invalidation cycle exercised via test_is_root_profile_invalidation_drops_stale (cache populated, then dropped after simulated profile rename).
- `ensure_cron_project` per-profile isolation exercised via test_ensure_cron_project_creates_per_profile (two profiles → two distinct project_ids).
- Legacy migration covered: untagged projects with sessions inherit session profile; orphan projects fall back to 'default'; idempotent (no-op on second call).
- Cross-alias matching pinned: `_profiles_match('default', 'kinni')` returns True only when `kinni` is `is_default`.
- Source-string assertions pin the active-profile guards on `/api/projects/{rename,delete}` and `/api/session/move`.
### Opus-applied fixes (absorbed in-PR per release policy)
### Opus-applied fixes (absorbed in-release)
**From stage-293 review:**
- **SHOULD-FIX A (project migration startup race)**: `api/models.py:load_projects()` re-reads from disk after acquiring `_PROJECTS_MIGRATION_LOCK` and finding `_projects_migrated=True`. Without this, Thread B that read pre-migration could return stale untagged rows after Thread A migrated and wrote disk — a mutation route on those stale rows could silently overwrite the migration. Window is process-startup-only and very narrow; fix is 8 LOC.
- **SHOULD-FIX B (agent version `--dirty` symmetry)**: `_detect_agent_version()` now passes `--dirty` to `git describe --tags --always`, matching `_detect_webui_version()`. Operators with locally-modified agent checkouts now see the dirty marker.
**Already absorbed in #1629 (in-PR Opus pre-merge pass before staging):**
- **SHOULD-FIX #1 (renamed-root client cross-alias)**: removed the strict-equality client filter at `static/sessions.js:1853`. Server-side `_profiles_match` cross-aliases `'default'`-tagged rows to a renamed root `'kinni'`; a strict-equality client filter would have rejected them, dropping every legacy session for renamed-root users. Server is now solely authoritative for profile scoping. Same fix applied to the `otherProfileCount` client fallback.
- **SHOULD-FIX #2 (messaging-source dedupe ordering)**: moved `_keep_latest_messaging_session_per_source(merged)` to AFTER the profile filter at `api/routes.py:2078`. Before: the dedupe ran on the merged-cross-profile list with profile-blind keys, discarding the older profile's row across profiles, then the profile filter scoped to the active profile — leaving zero rows for any messaging identity the active profile shared with another profile. After: filter first, then dedupe within scope.
+1 -1
View File
@@ -2,7 +2,7 @@
> Web companion to the Hermes Agent CLI. Same workflows, browser-native.
>
> Last updated: v0.50.292 (May 04, 2026) — 4142 tests collected
> Last updated: v0.50.293 (May 04, 2026) — 4180 tests collected
> Test source: `pytest tests/ --collect-only -q`
> Per-version detail: see [CHANGELOG.md](./CHANGELOG.md)
+2 -2
View File
@@ -1835,8 +1835,8 @@ Bridged CLI sessions:
---
*Last updated: v0.50.292, May 04, 2026*
*Total automated tests collected: 4142*
*Last updated: v0.50.293, May 04, 2026*
*Total automated tests collected: 4180*
*Regression gate: tests/test_regressions.py*
*Run: pytest tests/ -v --timeout=60*
*Source: <repo>/*
+10 -1
View File
@@ -1053,7 +1053,16 @@ def load_projects(*, _migrate: bool = True) -> list:
with _PROJECTS_MIGRATION_LOCK:
# Re-check inside the lock — another thread may have raced.
if _projects_migrated:
return projects
# Per Opus advisor on stage-293: another thread completed
# migration and wrote new state to disk while we waited for
# the lock. Our `projects` snapshot is the pre-migration
# version; re-read so the caller doesn't see stale untagged
# rows (which a mutation route could then write back,
# silently overwriting the migration).
try:
return json.loads(PROJECTS_FILE.read_text(encoding='utf-8'))
except Exception:
return projects
if _backfill_project_profiles_if_needed(projects):
try:
save_projects(projects)
+4 -1
View File
@@ -135,7 +135,10 @@ def _detect_agent_version() -> str:
# file is available (common in source checkouts and developer environments).
if not Path(_AGENT_DIR).exists():
return 'not detected'
out, ok = _run_git(['describe', '--tags', '--always'], _AGENT_DIR, timeout=3)
# Symmetric with _detect_webui_version() above — `--dirty` flags a
# locally-modified checkout so operators can see when their agent has
# uncommitted changes vs a clean tag. Per Opus advisor on stage-293.
out, ok = _run_git(['describe', '--tags', '--always', '--dirty'], _AGENT_DIR, timeout=3)
if ok and out:
return out