Fix edit_file corruption and matching, shell timeout process groups, grep/glob read boundary - #24
Conversation
MCP stdio held a lock for the full RPC duration, so Close/Refresh blocked when IDA or another backend hung, leaving zombie children and freezing the web UI. Chat also re-mapped the entire transcript every frame and grew unbounded reasoning strings during high-effort turns. Release the send lock while waiting for responses, kill+Wait on Close, batch stream patches by message only, and make live reasoning display configurable via display.show_reasoning and display.max_live_reasoning_chars.
Reasoning traces are long decompiler-style text. Rendering them through the chat Markdown pipeline on expand created hundreds of React nodes and locked the main thread (Chrome "Page Unresponsive"). Show reasoning as plain pre-wrap text in a height-capped scroller, and defer the body to the next frame so the toggle stays responsive.
…-misses Most edit_file failures are model-side: short old_string hits many sites, or stale/wrong identifiers (entity vs attachEntity) never exist in the file. Surface occurrence line numbers when the match is ambiguous, and near-miss file lines when nothing matches, so the agent re-reads instead of inventing. Also steer the prompt to require unique context and prefer edit_file over sed.
…and near-misses" This reverts commit 164078d.
A foreground command that outlived its timeout kept running behind the persistent shell, holding its pipes and corrupting every later call in the session. Put the shell in its own process group, kill the whole group on timeout or cancellation, and hand the next call a fresh shell. configureProcessGroup now preserves existing SysProcAttr settings instead of clobbering sandbox flags. Co-authored-by: Cursor <cursoragent@cursor.com>
The adjacent-insertion recovery replaced the first substring occurrence of the matched line anywhere in the file, corrupting an unrelated line mid-text while reporting success; it now splices by the matched line's byte range and never combines with replace_all. Matching tries the verbatim old_string before any EOL normalization, fileEOL picks the majority flavor so one stray CR/CRLF cannot poison every multi-line edit, and NUMBER| prefix stripping requires consecutive numbers so pipe-delimited data is never mangled. read_file no longer misreads a rune split at the 400 KB cap as binary and displays lone-CR files per line. Co-authored-by: Cursor <cursoragent@cursor.com>
grep and glob refused paths outside the workspace even in project sessions where read_file and list_files may read anywhere, which broke searches over the very files the agent could read. Both now resolve through the same read boundary. grep also reports when a line exceeds the scanner buffer instead of silently presenting the rest of the file as match-free. Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com> # Conflicts: # internal/config/config.go # internal/mcp/client.go # internal/tools/shell.go # web/src/pages/ChatPage.tsx
…savers The test asserted a guarantee the handler never made. handleModelSet saves in a detached goroutine, so twenty overlapping switches land on disk in any order; demanding that "model-19" specifically won the race failed whenever the scheduler finished the savers out of order. It reproduced reliably at GOMAXPROCS=2 and was the sole red check on PRs #23 and #24. Two changes: - Assert what IS ordered (the in-memory pointer swap must show the last switch) and only that SOME save landed, not which one. - Use a manual temp dir and drain the savers before removing it. The detached goroutines outlive the test body, so t.TempDir's RemoveAll was racing them into "directory not empty" — the failure the deleted mid-test os.Remove was papering over. Stress: 40x at GOMAXPROCS=2 green (was ~5 failures in 30), and 10x under -race. Runtime drops from 48s to 3s. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Review follow-ups to PR #24. The adjacent-insertion recovery reports success, so every case where it guesses wrong is a silent file corruption the model never learns about. Two of these were reproduced against the merged code before fixing. 1. A NUMBER| paste reached the fuzzy path. The prefix stayed in the anchor's token set, so a stale "12|- **pool39v2** | 14 hand |" still scored high against the real line, and the new row was written to the file carrying its literal "13|" prefix — reported as "1 replacement(s)". Prefixed pastes now never reach the guess; exact matching already handles them properly via stripReadFileLinePrefixes. 2. A deleted anchor spliced onto a surviving sibling row. Rows of one table share nearly every token by construction, so a removed row cleared both the 0.78 similarity floor and the 0.12 margin against its neighbour. Edit distance does not separate these either — "2026-02-01" vs "2026-03-01" is one character, exactly like a typo. What separates them is WHICH characters differ: digits are a line's identifying detail (dates, ids, versions, counts) and a rewording keeps them while a different row does not. Requiring identical digits admits the abbreviation case this recovery exists for and rejects the sibling-row confusion. A bounded Levenshtein check backs it up. 3. read_file and edit_file disagreed on a lone CR. read_file split on every CR while the file's own regression test treats an embedded CR as data, so read_file handed the model line numbers that do not exist in the file — manufacturing the stale anchors this PR set out to remove. Both sides now split on a lone CR only in a genuinely CR-terminated file. 4. kill(-pid) had no fallback. It addresses "the group led by pid", so a shell that never became a group leader would take the antares daemon down with it. Every path calls configureProcessGroup today and nothing enforces that; the PGID is now verified against our own before the negative kill, falling back to signalling the single process. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Merged as 8ccfe98. Reviewed the byte-range splice, the majority The red Four defects found in review are fixed on top in 9e3710f, two of them reproduced against your merged code first:
Regression tests for all four are in |
Summary
replace_all.old_stringis tried before any EOL normalization,fileEOLpicks the majority flavor (one stray CR/CRLF no longer poisons every multi-line edit on a file), andNUMBER|prefix stripping requires consecutive numbers so pipe-delimited data is never mangled. Adds stale-edit diagnostics: ambiguous matches list their line numbers, near-miss lines are surfaced, mixed pastes are called out.configureProcessGrouppreserves existingSysProcAttrinstead of clobbering sandbox flags.read_file(project sessions may search outside the workspace); grep reports lines exceeding the scanner buffer instead of silently presenting the rest of the file as match-free.Merged latest
main(PowerShell terminal protocol, MCP background reader, fast model switching) with conflicts resolved to keep both sides' fixes:killLockedkeeps main's reap-wait but upgrades to a process-group kill;mcp/client.gotakes main's reviewed implementation wholesale.Test plan
go build ./...,go vet, fullgo test ./...— green except pre-existinginternal/llmlive tests requiring a valid OpenAI key;TestStdioRoundTrip/TestModelSetConcurrentWithConfigReadsverified flaky-under-load only (pass isolated and on retry, identical code to main)make install-clibuild with dashboard embedded; daemon restarted and healthy on the new binaryMade with Cursor