Release CY — v0.51.127 (stage-batch9, 7-PR low-risk batch: brick-class Linux + brick-class update + composer wide-screen + Turkish + MCP toggle + SSE + Windows CI)#2860
Merged
Conversation
…n 1) Per @nesquena-hermes review on #2811: hermes-agent isn't published to PyPI, so `pip install hermes-agent` finds nothing and start.ps1's hermes_cli guard correctly bails out — leaving the previous workflow unable to self-validate against release/stage-batch6. This rework adopts option 1 from the review: drop the pip install, stub a hermes_cli/ directory with a minimal __init__.py next to the sibling hermes-agent/ folder, then run start.ps1 for 8 seconds and assert that none of its own Write-Error guards (no Python, no agent dir, bad port, missing hermes_cli, missing server.py) appeared in stderr. /health is no longer probed — the server cannot boot on a stub, and full-boot regressions stay covered by the Linux jobs and docker-smoke.yml. Scope intentionally narrower than the original: this workflow validates start.ps1's PowerShell syntax + path discovery only. The exact bug class PR #2805 caught (WOW64 ProgramFiles redirect) would now light up red here pre-merge, which is the reason this gate exists. Paths filter trimmed to `start.ps1` + the workflow itself; the broader list (requirements.txt / bootstrap.py / server.py) was inherited from the original full-boot scoping and isn't relevant for a path-discovery- only run. Verification: workflow runs on this PR via its own pull_request trigger. The first CI run on this branch IS the verification. CHANGELOG updated under [Unreleased] with a single bullet sized to the surrounding density.
The path-discovery step succeeds on the first run, but the cleanup
step exits non-zero because `taskkill /PID 5560 /T /F` returns 128
("process not found") when server.py has already exited on the mock
hermes_cli stub. That's the expected steady state for this mock-only
workflow, not a failure.
Two-line fix: reset `$global:LASTEXITCODE = 0` after the taskkill
call, and explicit `exit 0` at the end of the step so any other
external-command exit codes don't bubble up. The try/catch wrapper
didn't help because taskkill writes its diagnostic to stderr without
raising a PowerShell exception — `catch` never fired.
Run 26352805510 on this branch shows the failure shape: "OK: start.ps1
path discovery - all guards passed." in the verify step, then
"ERROR: The process '5560' not found." in the cleanup step. Path
discovery is what this workflow exists to validate; cleanup just has
to not fail the job.
`.composer-box` had a hardcoded `max-width: 780px` since the early v0.50.x layout pass. On wide displays (1440p+, 2880px ultrawides) this leaves significant unused horizontal space AND squeezes the composer-footer chips (workspace, model, reasoning, context %) against each other inside the 780px box. When the context-percentage ring appears (active token usage), the workspace chip truncates to "Fou..." instead of showing the full workspace name. Model + reasoning chips also lose room. The chip strip horizontally-scrolls inside .composer-left, so the rightmost chips effectively hide behind context %. The constraint isn't "Reading flow looks better at 780px" — the textarea is min-height:64px, max-height:200px and wraps naturally, so users on wide displays get the SAME readable text wrap regardless of box width. Only the footer chips suffer. Fix: clamp(780px, 60vw, 1100px). Preserves the 780px floor (no regression on viewports < 780px since clamp's first arg is the minimum) while letting wider viewports use up to 1100px (60% of viewport width, capped). 1100px gives ~40% more horizontal room for the footer chips without filling the entire screen at extreme widths. Per-viewport behavior: <= 780 px → 780 px (hard floor) — zero change vs current 1280 px → 60vw = 768 → floored to 780 — zero change 1440 px → 60vw = 864 — +84 px room 1920 px → 60vw = 1152 → capped at 1100 — +320 px room 2880 px → 60vw = 1728 → capped at 1100 — +320 px room One line in static/style.css. CHANGELOG entry. No JS. No new deps. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three small fixes from Copilot's review: 1. static/style.css:1354 - removed spaces inside `clamp(...)` args to match the file's existing compact style (no spaces after commas in neighboring declarations like `transition:border-color .2s,box-shadow .2s`). 2. CHANGELOG.md - wrapped the long single-line entry across multiple lines with standard Markdown continuation indentation for cleaner diffs. 3. CHANGELOG.md - normalized `~1300 px` to `~1300px` for unit-formatting consistency. No behavior change. Same one-line CSS rule, just tightened formatting. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…n wide displays by @Koraji95-coder
…ux shell Fixes #2853. The `_terminal_shell_preexec_fn` added in `71d8a8fb` called `prctl(PR_SET_PDEATHSIG, SIGTERM)` so orphaned PTY shells would die when the WebUI process crashed. But that signal is **per-thread**, not per-process, and WebUI runs `ThreadingHTTPServer`: every HTTP request is handled in its own short-lived worker thread. Flow that broke every Linux user: 1. User clicks the terminal toggle → frontend hits `POST /api/terminal/start`. 2. ThreadingHTTPServer spins up a worker thread to handle that one request. 3. The worker thread calls `subprocess.Popen(..., preexec_fn=...)`. 4. The shell calls `prctl(PR_SET_PDEATHSIG, SIGTERM)` in its preexec_fn. Its registered "parent" is now the WebUI worker thread that called Popen. 5. The handler returns its JSON response and the worker thread exits. 6. The kernel sees the pdeathsig-parent thread has died and sends SIGTERM to the PTY shell. The shell dies within ~10 ms of being created. 7. The reader loop sees EIO on the master FD, emits `terminal_closed`, and the frontend writes `[terminal closed]`. macOS users were unaffected because `libc.prctl` doesn't exist there — `ctypes.CDLL(None)` returns a libc handle, `libc.prctl` raises `AttributeError`, the bare-`except` swallows it, and the shell starts with no pdeathsig configured. Empirical verification on this Linux host (real PTY + `subprocess.Popen` inside a `threading.Thread` that joins immediately): with preexec_fn → proc.poll() == -15 (SIGTERM), master FD returns EIO without preexec_fn → proc.poll() == None (alive), master FD returns "HELLO\\r\\n" Same shell, same PTY, same threading topology as WebUI. Fix --- Drop the `preexec_fn` entirely. The orphan-shell-on-crash case the original PR was navigating is rare for self-hosted single-user installs, and the existing `atexit.register(close_all_terminals)` + explicit `close_terminal` paths cover graceful shutdown. A future fix (option B in the issue) can re-introduce pdeathsig pinned to a long-lived supervisor thread, but that is a follow-up — this PR is the smallest unbricks-Linux-today change. Tests ----- - Invert `test_terminal_shell_uses_parent_death_signal_preexec` → `test_terminal_shell_does_not_use_pdeathsig_preexec`: asserts `preexec_fn` is NOT in the Popen kwargs. - Add `test_pty_shell_survives_when_spawning_thread_exits`: spawns a real PTY shell via `start_terminal` from a worker thread, waits for the worker to join, asserts the shell is still alive after a half-second grace window. This is the contract the original tests never exercised. - Update `test_terminal_module_registers_graceful_shutdown_reaper` to refuse re-introduction of the preexec_fn or the `libc.prctl(1, SIGTERM)` call (treats either as a regression). All 27 terminal-related tests pass locally. Refs #2853
… that killed every Linux shell (#2853) by @nesquena-hermes
… latest tag Fixes #2846. After PR #2758 (the #2653 fix) the update check correctly falls through to the branch comparison when HEAD has moved past the latest `v*` tag — so the banner reports the real commit count against `origin/<branch>`. But `_select_apply_compare_ref` was never updated to mirror that decision: as long as any `v*` tag exists, it returns `tags[0]`, even when HEAD is far past it. Result for everyone running hermes-agent past `v2026.5.16` (i.e. anyone on agent master between tagged releases): 1. Banner: `Agent (origin/main): 254 updates available` ← correct 2. User clicks Update Now 3. `_select_apply_compare_ref` picks `v2026.5.16` because tags exist 4. `git pull --ff-only origin v2026.5.16` — no-op (HEAD is already past it) 5. `_schedule_restart()` fires anyway, server bounces 6. Next check still reports 254 behind — banner reappears unchanged `apply_force_update` had the same bug, except worse: `git reset --hard v2026.5.16` would have actively rewound the user's checkout 254 commits. The root cause is the same bug class as #2653 — two parallel paths (`_check_repo_release` and `_select_apply_compare_ref`) that should make the same decision but didn't. Pre-fix, the "is HEAD past the latest tag?" predicate lived inline inside `_check_repo_release` only. Fix --- Extract `_head_is_past_latest_tag(path, current_tag)` and have both paths consult it. When HEAD is past the latest tag: - check path: release check returns None → branch check runs (#2653, unchanged behaviour, just refactored) - apply path: falls through to upstream / `origin/<branch>`, never the stale tag (#2846, new behaviour) Tests ----- - `test_select_apply_compare_ref_uses_tag_when_head_is_on_tag` — unchanged behaviour pinned: HEAD exactly on tag → advance to tag. - `test_select_apply_compare_ref_falls_through_when_head_is_past_tag` — the #2846 repro: HEAD = v2026.5.16 + 608 commits → advance to `origin/main`, not the tag. - `test_select_apply_compare_ref_no_tags_uses_upstream` — unchanged. - `test_select_apply_compare_ref_no_tags_no_upstream_uses_default_branch` — unchanged. - `test_check_and_apply_paths_agree_when_head_is_past_tag` — symmetry test, ensures the two paths can't drift apart again. All 21 tests in `tests/test_updates.py` pass locally (16 existing + 5 new). Refs #2846, #2653.
Add `PATCH /api/mcp/servers/{name}` endpoint that accepts `{"enabled": bool}`,
updates `mcp_servers.<name>.enabled` in config.yaml, and calls `reload_config()`.
Mirrors the existing DELETE pattern.
Also wire the previously-defined-but-unrouted `_handle_mcp_server_delete` into
`handle_delete`, and `_handle_mcp_server_update` into a new `handle_put` +
`do_PUT` in server.py — fixing a pre-existing bug where those handlers existed
but were never reachable over HTTP.
UI: add a toggle button in each MCP server row in the system settings panel
(panels.js). Clicking it calls PATCH and reloads the list. Toggle button is
styled with `.mcp-toggle-enabled` / `.mcp-toggle-disabled` CSS classes. The
`toggle_supported` flag in the list response is now `True`.
i18n: add 5 new keys (`mcp_enable_server`, `mcp_disable_server`,
`mcp_enabled_toast`, `mcp_disabled_toast`, `mcp_toggle_failed`) to all 9
non-English locales (English values as placeholder translations).
Tests: add `TestMcpToggle` class with 7 tests covering disable, enable,
404-not-found, empty name, missing field, response payload, and URL-encoded name.
Update `test_empty_config` and visibility panel assertions to reflect
`toggle_supported: True` and the new toggle button in panels.js.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… fall-through past the latest tag (#2846) by @nesquena-hermes
Add a complete Turkish locale to the WebUI and login page so users can select Türkçe in Settings, with speech recognition via tr-TR. Co-authored-by: Cursor <cursoragent@cursor.com>
Fix Copilot review issues in the tr locale: Korean string leaks,
placeholder order, stray quotes, broken {provider} tags, duplicate
English voice keys overriding translations, and remaining TODO strings.
Co-authored-by: Cursor <cursoragent@cursor.com>
Replace \\u2026 with \u2026 (and fix \\u2192/\\u2713) in the tr block so ellipsis renders as U+2026 instead of literal backslash-u text. Add a regression test guarding against double-escaped unicode sequences. Co-authored-by: Cursor <cursoragent@cursor.com>
…e-agent-updates keys Sibling-PR collision between #2772 (Turkish locale baseline) and #2776 (MCP enable/disable toggle) plus already-shipped master additions for open_in_vscode and ignore_agent_updates. Add Turkish translations for the 9 missing keys to restore locale-parity invariant: mcp_enable_server, mcp_disable_server, mcp_enabled_toast, mcp_disabled_toast, mcp_toggle_failed, open_in_vscode, open_in_vscode_failed, settings_label_ignore_agent_updates, settings_desc_ignore_agent_updates
MUST-FIX: - tests/test_2735_open_in_vscode.py: bump expected open_in_vscode locale counter from 10 to 11 (Turkish locale added in #2772). The bump fell out of an in-rebase test edit but never got committed; tagging without this would have shipped a failing test in the release commit. SHOULD-FIX inline: - api/updates.py: case-D drift in _select_apply_compare_ref. The original #2855 fix used latest_tag in the past-tag predicate; the check side uses current_tag (HEAD's nearest reachable tag) plus a 'behind == 0' gate. They drift when HEAD is on an OLDER release tag with commits on top AND a NEWER tag exists ('case D'): check correctly suggests advancing to the newer tag, but apply fell through to origin/<branch>. Mirror the check-side predicate exactly. Adds regression test test_select_apply_compare_ref_case_d_older_tag_with_commits_and_newer_tag_exists. - static/messages.js: post-await race guard in _restoreSettledSession. stream_end without preceding 'done' enters the settlement path, awaits /api/session, then sets _streamFinalized=true. If a late 'done' event arrives during that await, it sees _streamFinalized still false and double-runs the finalize. The guard returns early when done won the race, avoiding double renderMessages() + double notification. - server.py: CORS preflight Access-Control-Allow-Methods now includes PUT. #2776 wired PUT into the router for /api/mcp/servers/{name} but didn't update the OPTIONS response. Same-origin only in practice, but cosmetic completeness for CORS-aware deployments. Opus advisor verdict: all 5 risk areas reviewed, 1 MUST-FIX + 3 SHOULD-FIX all addressed inline. Net: +69/-9, no new architecture, no behavior risk.
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.
Release CY — v0.51.127 (stage-batch9, 7-PR low-risk batch)
What's in this batch
Seven PRs landing as one low-risk release:
Brick-class fixes (every-user severity)
fix(terminal): drop PR_SET_PDEATHSIG preexec_fn that killed every Linux shell (#2853) #2854 by @nesquena-hermes — Embedded terminal opens then immediately closes with
[terminal closed]on every Linux install (bug(terminal): Embedded terminal opens then immediately shows [terminal closed] on Linux (regression from 71d8a8fb) #2853).PR_SET_PDEATHSIGis per-thread, not per-process, andThreadingHTTPServerworker threads die after each request — so the PTY shell got SIGTERM'd within ~10ms of spawning. Fix: drop thepreexec_fnentirely. macOS users were unaffected (libc.prctl doesn't exist there). Closes bug(terminal): Embedded terminal opens then immediately shows [terminal closed] on Linux (regression from 71d8a8fb) #2853.fix(updates): apply path must follow check-side fall-through past the latest tag (#2846) #2855 by @nesquena-hermes — "Update Now" loops for every user past the latest agent tag (bug(updates): Agent Update Now loops — apply path aims at latest tag while check reports against origin/main #2846). The check side correctly fell through to branch comparison post-fix(updates): pass --force to git fetch --tags to recover from remote re-tags (#2756) #2758, but the apply side still returned
tags[0]—git pull --ff-only v2026.5.16no-op'd, server bounced, banner reappeared.apply_force_updatehad the same bug except worse (would havegit reset --hard'd backwards 254 commits). Fix: extract_head_is_past_latest_tagand have both check + apply paths consult it. Opus pre-release review caught a "case D" parameter-asymmetry drift and patched the apply-side predicate to mirror the check side exactly. Closes bug(updates): Agent Update Now loops — apply path aims at latest tag while check reports against origin/main #2846.Bug fixes
fix(chat): settle stream_end without done #2852 by @ai-ag2026 — Chat
stream_endnow settles from the persisted session whendonewasn't received, instead of leaving the active pane projecting liveThinking/ assistant DOM state indefinitely. Duplicatedoneevents are idempotent. Opus pre-release review added a post-await race guard inside_restoreSettledSessionfor the case where a latedonearrives during the settlement network roundtrip.ci(windows): add native-Windows startup E2E workflow #2811 by @Koraji95-coder — Native-Windows startup E2E CI workflow now self-tests on PR push, closing the post-feat: native Windows community-guide link + start.ps1 launcher (#1952) #2783 gap where Windows-only regressions (WOW64 ProgramFiles redirect, etc.) could only be caught after release. Reworked per maintainer feedback to use a stub
hermes_cli/__init__.pyrather thanpip install hermes-agent(not on PyPI).Visible UX change
max-widthis now responsive on wide displays:clamp(780px, 60vw, 1100px). The 780px floor preserves byte-identical layout at 1280px (Aron's laptop reference); 1440p gains ~84px; 1920p gains ~320px (composer chips stop squeezing). Mobile responsive logic untouched. Single CSS rule change. Telegram-approved at 1280/1440/1920/mobile viewports.New features
feat(i18n): add Turkish (tr) locale #2772 by @vaur94 — Complete Turkish (
tr) locale (~1,182 keys + login page strings +tr-TRspeech recognition). Stage build absorbed sibling-PR i18n collision with feat: PATCH /api/mcp/servers/{name} — enable/disable toggle #2776 by adding Turkish translations for 9 missing keys. Closes feat: add Turkish (tr) locale to WebUI i18n #2537 as superseded.feat: PATCH /api/mcp/servers/{name} — enable/disable toggle #2776 by @roryford — New
PATCH /api/mcp/servers/{name}endpoint with enable/disable toggle UI. Also fixes a pre-existing bug:_handle_mcp_server_deleteand_handle_mcp_server_updatewere defined but never wired into the HTTP router. DELETE wired intohandle_delete, PUT wired via newhandle_put/do_PUT. CORS allow-methods updated to includePUT(Opus pre-release nit). 5 i18n keys added to all 11 locales. 7 new tests.In-stage fixes (agent-applied during build)
open_in_vscode,settings_*_ignore_agent_updates).test_2735_open_in_vscode.pyexpected exactly 10 entries; Turkish locale brought it to 11._select_apply_compare_ref,_restoreSettledSessionpost-await race guard, CORS allow-methods PUT). Net +69/-9 across 5 files.Verification
node -cclean on all touched filesast.parseclean on all touched files--oursauto-resolver for sibling-PR CHANGELOG collisionsFollow-up issues to file
open_in_vscodeparity gap (predates this batch; Opus advisor flagged during review)Closes
🤖 Generated and reviewed end-to-end by the agent pipeline (Phase 0 fit-screen → stage build → pytest gate → Opus pre-release advisor → UX evidence → Telegram approval).