mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-09 13:21:42 +00:00
The `prompt.submit` handler in the TUI gateway lets a client trim the conversation back to a chosen user turn via `truncate_before_user_ordinal`. It validated only the upper bound (`ordinal >= len(user_indices)`) and never the lower one. A negative ordinal therefore sailed straight past the guard and fell into Python's negative indexing: `user_indices[-1]` resolves to the *last* user turn, so the history was silently sliced to everything before it and that truncated list was immediately committed to disk with `db.replace_messages`, which deletes and reinserts the whole row in one transaction. The impact is severe and unrecoverable: a single out-of-range value — from a client bug, a hidden/real user-message desync, or any present or future frontend that emits a relative ordinal — permanently destroys the user's conversation on disk instead of returning the intended `4018` error. Because the gateway is deliberately frontend-agnostic, it cannot assume the value is well-formed; it must validate it. The fix is minimal and safe: extend the existing guard to reject negatives on the very same error path the upper bound already uses. No in-memory history is mutated and no DB write happens for an invalid ordinal, so a bad value now fails closed with no data loss. The valid-ordinal path is untouched. N/A - [x] 🐛 Bug fix (non-breaking change that fixes an issue) - `tui_gateway/server.py`: in the `prompt.submit` handler, change the ordinal guard from `if ordinal >= len(user_indices)` to `if ordinal < 0 or ordinal >= len(user_indices)` so a negative ordinal is rejected with error `4018` before any history slice or `replace_messages` write occurs. Added a comment explaining the negative-indexing hazard. - `tests/test_tui_gateway_server.py`: add `test_prompt_submit_rejects_negative_truncate_ordinal`, which submits a `truncate_before_user_ordinal` of `-1` and asserts the handler returns `4018`, leaves the in-memory history intact, never marks the session running, and never calls `replace_messages`. Added the `pytest` import used by the new test's fail-fast guards. 1. Check out this branch and run `scripts/run_tests.sh tests/test_tui_gateway_server.py -- -k negative_truncate` — the new test passes. 2. Reproduce the bug: temporarily revert the guard to the old `if ordinal >= len(user_indices)` and rerun — the test fails because the handler truncates the history and starts a turn instead of returning `4018`. 3. Full file run: `scripts/run_tests.sh tests/test_tui_gateway_server.py` (the only failure is the pre-existing, environment-dependent `test_browser_manage_connect_default_local_reports_launch_hint`, which also fails on clean `main` when a Chromium browser is installed locally). - [x] I've read the [Contributing Guide](https://github.com/NousResearch/hermes-agent/blob/main/CONTRIBUTING.md) - [x] My commit messages follow [Conventional Commits](https://www.conventionalcommits.org/) (`fix(scope):`, `feat(scope):`, etc.) - [x] I searched for [existing PRs](https://github.com/NousResearch/hermes-agent/pulls) to make sure this isn't a duplicate - [x] My PR contains **only** changes related to this fix/feature (no unrelated commits) - [x] I've run `pytest tests/ -q` and all tests pass - [x] I've added tests for my changes (required for bug fixes, strongly encouraged for features) - [x] I've tested on my platform: macOS 15 (Darwin 25.5.0) - [x] I've updated relevant documentation (README, `docs/`, docstrings) — or N/A - [x] I've updated `cli-config.yaml.example` if I added/changed config keys — or N/A - [x] I've updated `CONTRIBUTING.md` or `AGENTS.md` if I changed architecture or workflows — or N/A - [x] I've considered cross-platform impact (Windows, macOS) per the compatibility guide — or N/A - [x] I've updated tool descriptions/schemas if I changed tool behavior — or N/A |
||
|---|---|---|
| .. | ||
| __init__.py | ||
| entry.py | ||
| event_publisher.py | ||
| git_probe.py | ||
| loop_noise.py | ||
| project_tree.py | ||
| render.py | ||
| server.py | ||
| slash_worker.py | ||
| transport.py | ||
| ws.py | ||