Conversation
teknium1
left a comment
There was a problem hiding this comment.
Thanks for adding a confirmation gate to a command that restarts the gateway. The premise is confirmed on current main: gateway/slash_commands.py:4611-4706 writes the update marker and starts the detached process without a prior confirmation.
Problems
gateway/run.py:14435passesallow_alwaysto every adapter invocation, including the default three-button flow. Platform adapters are an extension surface:gateway/platforms/ADDING_A_PLATFORM.md:121documents the existing optional signature. A deployed adapter with that signature raisesTypeError; the broad catch atgateway/run.py:14439-14443silently falls back to text and loses its native confirmation buttons.
Suggested changes
- Pass
allow_alwaysonly when it isFalse, and add a legacy-adapter regression test for the default path. That preserves existing adapter behavior while allowing/updateto use text fallback where a platform has not adopted the two-button extension.
Automated hermes-sweeper review.
| session_key=session_key, | ||
| confirm_id=confirm_id, | ||
| metadata=metadata, | ||
| allow_always=allow_always, |
There was a problem hiding this comment.
Passing this new keyword even when it is True breaks already-deployed external adapters implementing the documented pre-change signature; the catch below turns their TypeError into a silent text fallback. Please only add this kwarg when allow_always is False, and cover a legacy-signature adapter in the default path.
There was a problem hiding this comment.
Fixed in f726277f5: allow_always is now only added to kwargs when it's False (non-default); legacy adapters never see the new keyword. Added test_default_allow_always_not_passed_to_legacy_adapter to cover the default path. All 47 tests in test_update_slash_confirm.py + test_cmd_update.py pass.
|
CI is green on
Both pre-existed on the original commit (confirmed by reproducing locally before either fix), unrelated to the |
/update pulls new code and restarts the running gateway, interrupting every active session on the host — but it fired the instant the command was received, with no confirmation. An accidental or fat-fingered invoke had no undo. /update now ALWAYS routes through the existing slash-confirm primitive before spawning: native Approve Once / Cancel buttons on Telegram, Discord, and Slack, with a text fallback elsewhere. There is deliberately NO 'Always Approve' button and no config opt-out — unlike /reload-mcp and the destructive session commands (/new, /reset, /undo), where a permanent one-tap opt-out is reasonable because they run often and are low-stakes. /update is rare and high-stakes: a permanent one-tap disable would reintroduce the exact accidental-fire footgun this confirmation exists to close. Mechanism: - Widen the shared send_slash_confirm hook (base + Telegram/Discord/ Slack/WhatsApp adapters) and _request_slash_confirm with allow_always: bool = True. When False, the middle 'Always Approve' button is suppressed. Default True keeps /reload-mcp and the destructive commands byte-for-byte unchanged. - /update passes allow_always=False and its handler treats any non-cancel choice as proceed-once, persisting nothing. - Cheap pre-flight validation (platform allowlist, managed-install, git-repo, hermes-binary) still fast-fails BEFORE prompting. - Detached-spawn mechanics extracted verbatim into _execute_update; the confirm handler calls it on approval. No change to the spawn. Tests: - tests/gateway/test_update_command.py: spawn-mechanics tests now call _execute_update directly (the post-approval unit); validation/ platform-gate tests still hit _handle_update_command with a stubbed confirm hook. - tests/gateway/test_update_slash_confirm.py: always-prompts, no-always- button, allow_always=False forwarded to adapter, once/cancel resolution, defense-in-depth that a stray 'always' proceeds once and never persists, and pre-flight-beats-prompt. 201 tests pass across the update, slash-confirm, destructive-confirm, telegram, whatsapp, and discord suites; all touched modules import clean. (cherry picked from commit d5c54a7136dd480028672b91af4868ce431f05ea)
…True for legacy compat Bot review flagged that allow_always was passed to every adapter invocation including legacy ones without the parameter. A legacy adapter would raise TypeError and silently fall back to text, losing native confirmation buttons. Gate the kwarg: only include allow_always when it is False (non-default). Legacy adapters use their own default (True) and stay unaffected. Test added: test_default_allow_always_not_passed_to_legacy_adapter
…ale catalogs The /update confirmation prompt (feat: confirmation prompt for gateway /update command) added gateway.update.confirm_prompt and gateway.update.cancelled to locales/en.yaml only, missing the other 15 locale catalogs. test_i18n.py::test_catalog_keys_match_english caught the drift. Translated both keys into af, de, es, fr, ga, hu, it, ja, ko, pt, ru, tr, uk, zh-hant, zh — matching the tone and structure of the existing gateway.reload_mcp.confirm_prompt / cancelled keys already translated in each catalog.
…flow refactor /update now always routes through the slash-confirm primitive before spawning (_handle_update_command -> _request_slash_confirm -> _execute_update on approval). test_update_command.py's spawn-mechanics tests were updated to call _execute_update directly, but this sibling test in test_update_streaming.py still called _handle_update_command and asserted on subprocess.Popen — which is never reached before approval, making mock_popen.call_args None. Update the test to call _execute_update directly, mirroring the pattern already used in test_update_command.py.
The 2026-08-25 upstream commit a96f7c8 added a 16th locale (ar.yaml) after our original i18n fix covered 15. This adds the two new keys (confirm_prompt, cancelled) to ar.yaml with Arabic translations, matching the tone of the existing ar.yaml gateway.update block.
After the confirm-flow refactor, /update always routes through the confirm gate before spawning. These two tests in test_update_command.py were calling _handle_update_command directly (which now returns early after the confirm prompt) instead of _execute_update (the post-approval path). Updated both to call _execute_update directly, matching the pattern used in test_update_streaming.py and test_update_slash_confirm.py.
e422431 to
8c810c1
Compare
|
Rebased onto current What changed in the rebase:
Commit history (6 commits on main): CI should be green on the new head. The 1 remaining test failure ( |
HERMES_MANAGED=homebrew is in _IGNORED_MANAGED_VALUES (upstream intentionally ignores homebrew since Hermes self-updates via its own brew tap). The test's intent — managed installs block before the confirm prompt — is still valid; it just needs a non-ignored value. Switched to nix, which IS blocked.
|
Fixed the one remaining CI failure: Root cause: The test used Fix: Switched the test to use New head: |
|
Closing — the feature in this PR is already on The Verified on
Nothing here is missing from |
/##
/updatefires instantly -- no "are you sure?"/updatepulls new code and restarts the running gateway, interrupting everyactive session on the host -- but it fired the instant the command was received,
with no confirmation. An accidental or fat-fingered invoke had no undo.
This PR: always prompt, never persist approval
/updatenow always routes through the existing slash-confirm primitivebefore spawning: native Approve Once / Cancel buttons on Telegram,
Discord, and Slack, with a text fallback elsewhere.
There is deliberately no "Always Approve" button and no config opt-out --
unlike
/reload-mcpand the destructive session commands (/new,/reset,/undo), where a permanent one-tap opt-out is reasonable because they runoften and are low-stakes.
/updateis rare and high-stakes: a permanentone-tap disable would reintroduce the exact accidental-fire footgun this
confirmation exists to close.
Mechanism
send_slash_confirm(base + Telegram/Discord/Slack/WhatsApp)allow_always: bool = True; whenFalse, the middle button is suppressed_request_slash_confirm(run.py)allow_always/updatehandlerallow_always=False; always prompts; any non-cancel choice proceeds once, persists nothinglocales/en.yamlDesign decisions
allow_always=Truemeans/reload-mcpand the destructive commandsare byte-for-byte unchanged -- this is a generic widening of the shared
hook, not a special-case.
hermes-binary) still fast-fails before prompting.
_execute_update; theconfirm handler calls it on approval. No change to the spawn.
Tests
tests/gateway/test_update_command.py: spawn-mechanics tests call_execute_updatedirectly (post-approval); validation/platform-gate testsstill hit
_handle_update_commandwith a stubbed confirm hook.tests/gateway/test_update_slash_confirm.py: always-prompts,no-always-button,
allow_always=Falseforwarded to adapter, once/cancelresolution, defense-in-depth that a stray "always" proceeds once and never
persists, and pre-flight-beats-prompt.
Telegram, Discord, and Slack suites.