Fix 24 correctness, robustness, and security issues across the core crates#79
Merged
Conversation
…rates
Findings from a full review of hi-agent, hi-ai, hi-tools, hi-cli, hi-lsp,
and hi-tui (mlx/cuda excluded). Every fix was verified against a concrete
failure scenario; the full suite (829 tests) passes.
Crashes on untrusted input:
- Streamed text containing `<|>` produced a reversed slice range and
panicked in strip_special_tokens_chunk/strip_special_tokens (hi-ai) and
strip_chatml_tokens (hi-agent); search the suffix after the `<|` prefix
- Bound SSE tool-call/content-block indices (OpenAI + Anthropic) so one
corrupt frame can't force a multi-GB resize_with allocation
- Char-safe API-key masking in --show-config and /provider edit; char-safe
Ctrl-R history preview truncation in the TUI
Silent data loss / corruption:
- Profile serializer omitted six fields (minimal_tools, curate_skills,
explore_subagents, write_subagents, planner_model, skeptic_model) —
every config save deleted them; switch to derived Serialize
- /provider edit (REPL + TUI) wiped fields the form doesn't cover; edits
now start from the existing profile
- edit/multi_edit/apply_patch corrupted non-UTF-8 files via lossy decode
+ write-back; they now refuse with a clear error
- Session JSONL appends split >8KB records across write() calls, letting
concurrent writers interleave mid-line; single write_all per record
- TUI explore-run merge overwrote committed prose lines; merge only while
the run's summary line is still the last transcript entry
- A malformed SSE frame after finish_reason discarded (and re-billed) a
fully-streamed answer; tolerated like the transport-error case
Security:
- web_download never SSRF-validated model-supplied URLs despite the module
doc claiming it did; validate before handing to curl/aria2c
Hangs and stuck states:
- LSP: serialize request round-trips (concurrent calls stole each other's
responses → 120s hangs); two-phase cancel-safe reads with poison-on-
desync + respawn (a timeout mid-frame permanently broke the stream);
invalidate the synced-doc map on respawn, /lsp off, and failed syncs
- Stale .memory.lock after a crash disabled memory writes forever; break
locks older than 10 minutes
- A Failed long-horizon goal never returned to Active on plan revision,
permanently disabling auto-drive
- Loop firings hung forever on a child that wedged after closing stdout;
bound child.wait() like run_fix does
- Bash output readers aborted on the first invalid-UTF-8 byte, losing
output and stalling until timeout on a full pipe; read bytes per line
Agent-loop correctness:
- replace_last_nudge matched a stale verify marker from a previous turn
and erased the current turn's transcript, leaving consecutive user
messages; replace only when the tail is exactly the prior nudge cycle
- explore/delegate/record_decision ran in a pre-pass before earlier
mutations in the same batch (stale tree for subagents, respects_deps
debug-assert panic); they now dispatch through the dep-aware scheduler
- Side model calls (finalize/skeptic/curate/memory/explore/compaction)
clobbered context_used, breaking the ctx gauge and auto-compaction
- Interrupt flag leaked past batch end, spuriously cancelling the next
round's tool calls
- Missing tool-call `index` collapsed parallel calls into slot 0
("readread", concatenated args); route by new-call heuristic instead
- Anthropic usage backfill now sets `estimated` and context_occupancy
- Memory bullets ending in a sentence period were misclassified as paths
and silently dropped by grounded verification
- REPL provider add/edit/remove honored --config instead of always
writing to the default config path
Known issues deliberately not addressed here: the merged global+local
config save design (can copy global profiles into ./hi.toml), redirect
re-validation in web_download, and the pre-existing shared-cwd race that
makes verify_skipped_for_prose_only_changes flaky under full-suite
parallelism.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This was referenced Jul 10, 2026
davidrhodus
added a commit
that referenced
this pull request
Jul 10, 2026
* Apply rustfmt to the review-fix code (unblock CI) The CI `fmt · test` job runs `cargo fmt --all --check` before the test step, and several hand-written lines from the recent review fixes (#79, #81, #82 and the loop-firing timeout) didn't match rustfmt — mostly long assert!/match/call expressions that rustfmt reflows across multiple lines. Format failing blocked the Test step from ever running, so CI showed red despite a green local suite. Formatting-only: `git diff -w` shows nothing but line reflows; the four touched crates' tests still pass. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Fix two clippy errors surfaced on stable 1.97 CI's stable toolchain moved to rust 1.97, whose tightened lints error (under -D warnings) on two pre-existing spots the 1.93 CI never flagged. Once the fmt fix let the Test job reach the Clippy step, they blocked it: - web.rs: redundant `&` in a format! arg (useless_borrows_in_formatting) - app/run.rs: sort_by comparator that is really a keyed reverse sort (unnecessary_sort_by) → sort_by_key(Reverse(..)) Both are behavior-preserving. Verified with the CI toolchain: `cargo +stable clippy -p hi-agent -p hi-ai -p hi-tools -p hi-tui -p hi -p hi-eval -- -D warnings` is clean, fmt --check clean, tests pass. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
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.
Summary
A correctness-focused review of the core and agentic-coding crates (
hi-agent,hi-ai,hi-tools,hi-cli,hi-lsp,hi-tui; mlx/cuda excluded) surfaced 24 major issues. Each was verified against a concrete failure scenario before fixing.Crashes on untrusted input
<|>slice panic: streamed text containing<|>(e.g. Haskell's alternative operator in a code answer) produced a reversed slice range and crashed the turn — in both hi-ai stream filters and hi-agent'sstrip_chatml_tokens. The suffix search now starts after the<|prefix."index": 9999999999) forced a multi-GB allocation in the OpenAI and Anthropic stream parsers. Indices are now capped at 512.--show-config,/provider edit) and the TUI's Ctrl-R history preview panicked on multi-byte characters; both now truncate by chars.Silent data loss / corruption
Serializeimpl omittedminimal_tools,curate_skills,explore_subagents,write_subagents,planner_model,skeptic_model— any/modelswitch or profile edit permanently destroyed them. Replaced with derived serialization so a new field can't be silently dropped again./provider editwiped uncovered fields (max_tokens,fallback,tool_mode, …) in both the REPL and TUI flows; edits now start from the existing profile.edit/multi_edit/apply_patchlossily decoded (e.g. Latin-1) files and wrote the mangled string back, destroying every non-ASCII byte file-wide. They now refuse with a clear error pointing atbash/iconv.write()calls, so twohi -cprocesses (or a fleet child on--session-file) could corrupt each other's lines — silently dropped on resume. Each record is now onewrite_allon theO_APPENDfd.⏺ read 6 files · 743 lines) could replace committed assistant prose; it now merges only while its own line is still the last entry.finish_reasonthrew away the fully-streamed answer and re-billed a retry.Security
web_downloadhad no SSRF check despite the module doc claiming protection — a prompt-injected URL could pull cloud-metadata/localhost content into a readable workspace file. It now runs the same validator asweb_fetch(initial hop; redirect re-validation noted as follow-up).Hangs and stuck states
iomutex, reads are two-phase (cancel-safe wait, then committed frame read) with poison-on-desync + respawn, and the dedup map is invalidated on respawn, disable, and failed syncs..memory.lockafter a SIGKILL disabled memory persistence for all future sessions; locks older than 10 minutes are now broken.rederive_statusnever returned aFailedgoal toActiveafter a plan revision activated new work, contradicting its own doc and disabling auto-drive.child.wait()is now bounded likerun_fix.Agent-loop correctness
replace_last_nudgeerased the current turn: a stale verify marker from an earlier turn triggered the pop path mid-turn, destroying the model's just-completed work and leaving consecutive user messages (invalid transcript; debug-assert panic). It now replaces only when the tail is exactly the prior nudge's response cycle.explore/delegate/record_decisionran before earlier mutations in the same batch, handing subagents a pre-edit tree (and trippingrespects_depsin debug builds). They now dispatch inside the dep-aware scheduler.context_used, so a ~3 K recap at the end of a 150 K session reported "ctx 2%" and silently skipped auto-compaction. Side calls now count spend without touching the gauge.indexcollapsed parallel calls into slot 0 ("readread", concatenated JSON args) on local OpenAI-compat servers; routing now uses a new-call heuristic.estimatedandcontext_occupancy; memory bullets ending in a sentence period are no longer misclassified as paths and dropped; REPL provider flows honor--config.Test plan
cargo test -p hi-agent -p hi-ai -p hi-tools -p hi -p hi-lsp -p hi-tui— 829 tests passcargo clippy --all-targetson the six crates — only the four pre-existing hi-tui test warnings remain<|>panic and prose-bullet drop were reproduced standalone before fixingKnown issues left for follow-up
hi.tomlpresent,/modelcopies global profiles (including literal API keys) into the project file, and removing a globally-defined profile resurrects on next launch. Needs a decision on file ownership.verify_skipped_for_prose_only_changesis flaky under full-suite parallelism (pre-existing): tests share the crate cwd, the verifier snapshots it, and a concurrent test's extension-less scratch file mid-window makes the change-set look non-prose. Harness refactor needed.web_downloadfollows redirects without re-validating (initial-hop check only).🤖 Generated with Claude Code