Release v6.3.5#88
Conversation
…uments Introduce tryParseToolArguments() that parses valid JSON verbatim and only repairs genuinely malformed JSON conservatively. Previously, a lenient regex that ran before parsing also matched "key": value patterns inside string values (e.g. "Content-Type": mimeType), turning valid JSON into invalid JSON and forcing fallback to regex-based partial extraction that preserved literal "\n" instead of intended newlines, corrupting file edits. Also fix removePartialBlock to match by toolUseId only instead of requiring partial === true, because by finalization time Task.ts has already flipped all partial blocks to partial:false.
…ismatches Split replacement strategies into two tiers: SAFE_REPLACERS (exact match, whitespace/normalization-flexible strategies) whose matches can be applied without risk, and ESCAPE_FUZZY_REPLACERS that locate old_string by normalizing escape sequences but deliberately do NOT apply the edit — instead they fail loudly with guidance to re-send old_string verbatim. In multiFileEditTool, remove the double-unescaping of old_string/new_string since escape sequences are already decoded at the transport boundary (JSON.parse for native tool calls). Also add a guard that rejects edits whose old_string matches text just inserted by a prior edit to the same file.
Execa spawns a fresh subprocess per command, so `cd` changes were lost between commands. Append `pwd -P` after each command, write the result to a temp file, and read it back to update the terminal's tracked cwd. Only applies on POSIX. Also set GIT_EDITOR=true in the subprocess env to prevent interactive git operations (commit/rebase) from hanging a non-interactive subprocess forever.
…one capture Introduce SpeechToTextRecorder that captures microphone audio and transcribes via the KiloCode API. Wire it through webviewMessageHandler with start/stop recording message types. Add useAudioRecorder hook on the webview side and associated message types (speechToTextRequest/Response). Language defaults to "en" to prevent Whisper from auto-detecting filler words as other languages.
When execute_command uses a custom message, the ask text includes a MESSAGE: prefix (e.g. 'MESSAGE:Installing\n---\nnpm install'). The auto-approval prefix matching was comparing this raw ask text against the stored allowedCommands patterns, so the match always failed for commands with custom messages. The 'Don't ask again' feature saved the command correctly but the subsequent auto-approval check never recognized it. Added extractCommandFromAskText() helper that strips the MESSAGE prefix and Output suffix, and applied it in getCommandDecisionForMessage and getDeniedPrefix so prefix matching operates on the actual command text.
…sk/approve-for-me/full-access
Introduce a three-tier command approval setting that users can select from
the chat textarea:
- "ask" -> prompt before every command
- "approveForMe" -> auto-approve commands the model marks as non-dangerous;
prompt for dangerous ones (default)
- "fullAccess" -> auto-approve every command without asking
Key changes:
- Add `commandApprovalMode` to the global settings schema and wire it
through `ExtensionState`, `WebviewMessage`, and `ClineProvider`.
- Refactor the `askApproval` branch in `presentAssistantMessage`:
extract the common "auto-approve without blocking" pattern into a
reusable helper that still surfaces the tool UI row but resolves the ask
promise immediately via setImmediate/yesButtonClicked.
- Only `execute_command` (ask type "command") now triggers a real
Run/Cancel prompt; every other tool always auto-approves.
- Add `isDangerous` param to `ExecuteCommandToolUse` in the shared type
definitions and the native-tool JSON schema.
- `executeCommandTool` reads the model-supplied `isDangerous` flag and
stashes it on `Task.pendingCommandIsDangerous`.
- The `presentAssistantMessage` command branch reads the mode + danger
flag to decide whether to auto-approve or prompt.
- Switching away from "fullAccess" clears the per-task
`autoApproveAllCommands` override so the new mode takes effect
immediately.
…chat textarea
Implement the frontend side of the configurable command approval mode:
- Add `CommandApprovalSelector` component with a dropdown offering the
three modes: Ask for Approval, Approve for Me (default), Full Access.
Each option displays an icon and description.
- Add HandIcon, ShieldUserIcon, and SecurityWarningIcon SVGs in customIcons
for the approval mode dropdown options.
- Register new icons in the select-dropdown icon map.
- Replace the KiloModeSelector with the CommandApprovalSelector in the
chat textarea bottom controls.
- Wire `commandApprovalMode` and `setCommandApprovalMode` through the
ExtensionStateContext with a default of "approveForMe".
- Add i18n strings for the three mode labels and descriptions.
…l prompts
Document the new `isDangerous` parameter in both the XML-style
(execute-command.ts) and native-tool JSON (execute_command.ts) prompt
templates:
- XML prompt: add `isDangerous` to Parameters list and Usage example.
- Native JSON schema: add boolean `isDangerous` property with description
of what makes a command dangerous (deletes/overwrites, force-push,
database migrations, permission changes, etc.).
Update package.json version from 6.3.0 to 6.3.5 to reflect the command approval mode feature and related changes.
There was a problem hiding this comment.
🧪 PR Review is completed: High-complexity release PR with substantive changes across parsing, tool execution, terminal cwd persistence, speech-to-text, and command approval modes. Found 3 issues: a race condition in speech recorder cleanup, a potential null dereference in the new tryParseToolArguments repair path, and a missing await in the terminal cwd persistence test helper that could mask real failures.
Skipped files
webview-ui/src/i18n/locales/en/chat.json: Skipped file pattern
⬇️ Low Priority Suggestions (4)
src/integrations/speech/SpeechToTextRecorder.ts (1 suggestion)
Location:
src/integrations/speech/SpeechToTextRecorder.ts(Lines 225-228)🔴 Concurrency / Resource Leak
Issue:
stop()setsthis.stopped = true, then immediately assignsthis.proc = undefined, but theprocreference is captured in a closure for theexitlistener and the timeout. Ifstop()is called twice in rapid succession (or ifstart()hasn't finished yet), the second call will seethis.proc === undefinedand skip the graceful shutdown, leaving the first call'sprocpotentially orphaned. More critically, theenqueuechain uses.catch(() => {})which swallows all transcription errors silently, making debugging impossible.Fix: Guard
stop()with an early-return if already stopped, and avoid swallowing errors in the transcription chain.Impact: Prevents orphaned ffmpeg processes and makes STT failures observable.
- async stop(): Promise<void> { - this.stopped = true - const proc = this.proc - this.proc = undefined + async stop(): Promise<void> { + if (this.stopped) { + return + } + this.stopped = true + const proc = this.proc + this.proc = undefined +
src/core/assistant-message/AssistantMessageParser.ts (1 suggestion)
Location:
src/core/assistant-message/AssistantMessageParser.ts(Lines 53-53)🟡 Null Safety / Logic Error
Issue: In
tryParseToolArguments, the repair regex path doesconst repaired = rawArgs.replace(...)and then checksif (repaired !== rawArgs). However, ifrawArgsisundefinedornull(defensive coding), the.trim()on line 37 would throw before reaching here. More realistically: thecatchon line 47 catchesJSON.parsethrowing, but ifrawArgsis an empty string'',.trim()returns'', thestartsWithcheck fails, and it returnsundefined— that's fine. The real issue is that the repair regex can match inside string values that contain colons, e.g."key": valuepatterns inside a JSON string value. The comment block above acknowledges this was the old bug, but the new regex/("([^"]+)"\s*:\s*)([a-zA-Z0-9_.*\/\-]+)(?=\s*[,\]}])/gstill uses"([^"]+)"which will match any quoted key, including those inside string values, if the value after the colon is a bare scalar. The comment says "conservative repair" but the regex is still vulnerable to matching inside string values that happen to contain"something": scalarpatterns.Fix: Add a negative lookbehind to ensure we're not inside a string value, or more practically, validate that the match position is not inside a quoted string. Given the complexity, at minimum add a guard that the key is at the top level or inside an object, not inside a string value.
Impact: Prevents the exact corruption bug (literal
in files) from reoccurring if the model emits nested JSON-like strings.- const repaired = rawArgs.replace(/("([^"]+)"\s*:\s*)([a-zA-Z0-9_.*\/\\-]+)(?=\s*[,\]}])/g, '$1"$3"') + const repaired = rawArgs.replace(/(?<![^\\]\\)("([^"]+)"\s*:\s*)([a-zA-Z0-9_.*\/\\-]+)(?=\s*[,\]}])/g, '$1"$3"') +
src/integrations/terminal/__tests__/ExecaTerminalCwdPersistence.spec.ts (1 suggestion)
Location:
src/integrations/terminal/__tests__/ExecaTerminalCwdPersistence.spec.ts(Lines 16-30)🟡 Async / Test Reliability
Issue: In
runCommand, theonCompletedcallback capturesoutput = out ?? "", but the promise resolves immediately whenterminal.runCommand(...).then(() => resolve(output))fires. However,onCompletedis a callback that may fire before the process promise resolves, or there may be a race whereoutputhasn't been assigned yet. More importantly,runCommanddoesn'tawaitanything inside the promise constructor — it just attaches.thentoprocess. Ifterminal.runCommandresolves beforeonCompletedfires,outputwill still be empty. The helper should await the completion callback explicitly.Fix: Use a Promise that resolves from
onCompleteddirectly, not fromprocess.then.Impact: Prevents flaky tests where
outputis captured before the callback fires.- function runCommand(terminal: ExecaTerminal, command: string): Promise<string> { - return new Promise<string>((resolve, reject) => { - let output = "" - const callbacks: RooTerminalCallbacks = { - onLine: () => {}, - onCompleted: (out) => { - output = out ?? "" - }, - onShellExecutionStarted: () => {}, - onShellExecutionComplete: () => {}, - } - const process = terminal.runCommand(command, callbacks) - process.then(() => resolve(output)).catch(reject) - }) - } + function runCommand(terminal: ExecaTerminal, command: string): Promise<string> { + return new Promise<string>((resolve, reject) => { + let output = "" + const callbacks: RooTerminalCallbacks = { + onLine: () => {}, + onCompleted: (out) => { + output = out ?? "" + resolve(output) + }, + onShellExecutionStarted: () => {}, + onShellExecutionComplete: () => {}, + } + terminal.runCommand(command, callbacks).catch(reject) + }) + } +
src/core/tools/fileEditTool.ts (1 suggestion)
Location:
src/core/tools/fileEditTool.ts(Lines 933-938)🟡 Code Quality / Maintainability
Issue: The new
scanReplacersfunction andperformReplacementrefactor introduceReplacerScanResultand split replacers intoSAFE_REPLACERSandESCAPE_FUZZY_REPLACERS. However,sourceCodeEscapeReplacer(inESCAPE_FUZZY_REPLACERS) still contains logic that can yield matches which, if applied, would corrupt escape sequences. The PR correctly prevents application by only using these replacers for detection, but the naming and separation is fragile — future maintainers might mistakenly movesourceCodeEscapeReplacerintoSAFE_REPLACERS.Fix: Add a prominent comment on
sourceCodeEscapeReplaceritself warning that it must NEVER be inSAFE_REPLACERS, and renameESCAPE_FUZZY_REPLACERStoDETECT_ONLY_ESCAPE_REPLACERSto make the intent unmistakable.Impact: Prevents future regressions where escape-normalizing replacers get accidentally promoted to safe/application paths.
- // ESCAPE-FUZZY replacers: match by normalizing escape sequences (literal "\n" - // vs real newline, escaped quotes, etc.). These are NEVER applied — a match - // here means old_string's escaping disagrees with the file, which is exactly - // the condition that produces silent file corruption. performReplacement uses - // them only to detect that case and fail loudly with guidance. - const ESCAPE_FUZZY_REPLACERS: Replacer[] = [sourceCodeEscapeReplacer, escapeNormalizedReplacer] + // ESCAPE-FUZZY replacers: match by normalizing escape sequences (literal "\n" + // vs real newline, escaped quotes, etc.). These are NEVER applied — a match + // here means old_string's escaping disagrees with the file, which is exactly + // the condition that produces silent file corruption. performReplacement uses + // them only to detect that case and fail loudly with guidance. + const DETECT_ONLY_ESCAPE_REPLACERS: Replacer[] = [sourceCodeEscapeReplacer, escapeNormalizedReplacer] +
What's Changed
Features
highalwaysFixes
Documentation
executeCommandtool: addedisDangerousparameter documentationChores
Full Changelog:
v6.2.6...v6.3.5