fix(cli): escape @ symbols on paste to prevent unintended file expansion - #21239
#21239fix(cli): escape @ symbols on paste to prevent unintended file expansion#21239scidomino merged 1 commit intogoogle-gemini:maingoogle-gemini/gemini-cli:mainfrom krishdef7:fix/paste-at-escape-completekrishdef7/gemini-cli:fix/paste-at-escape-completeCopy head branch name to clipboard
Conversation
Summary of ChangesHello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request significantly enhances the robustness of the CLI by improving how Highlights
Changelog
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
|
Follow-up commits removed an earlier duplicate paste insert and accidental debug files. Final implementation ensures:
Manual validation performed with: |
There was a problem hiding this comment.
Code Review
This pull request introduces a robust mechanism for handling @ symbols in pasted text to prevent them from being misinterpreted as @path commands. It correctly escapes them on input and un-escapes them for display and for the model. Additionally, it fixes an issue where transcripts were not flushed for tool-only responses, ensuring better state consistency for downstream hooks. The changes are logical and well-tested. However, I've identified a high-severity bug in the new un-escaping logic that can cause data corruption when escaped backslashes are involved. Please see the detailed comments for a fix.
|
Addressed earlier review feedback regarding escaped backslashes. test\@foo → test@foo |
39d6838 to
7e57513
Compare
|
All review feedback has been addressed. The high-severity backslash bug flagged by the bot is fixed via unescapeLiteralAt() in the new utils/unescapeLiteralAt.ts. The outdated inline comments refer to earlier versions of the code that no longer exist in the diff, conflicts have also been resolved |
|
This was already fixed last month in #18965 |
|
Actually, I realize this is slightly different. Reopening |
|
My policy is to not review PRs just because someone tagged me (since that's obviously not scalable if everyone does it). But I will make an exception in this case since I accidentally closed it. I don't think we want to enable this by default. There are many users who depend on this feature (I have many times in the past). Add a setting to control this that is disabled by default. |
|
Also, please clean up and shorten the description to a length that a human can actually read it. Follow the PR format in pull_request_template.md. |
e2771a4 to
88638c1
Compare
|
Addressed reviewer feedback:
|
scidomino
left a comment
There was a problem hiding this comment.
Even when it's escaped, we still highlight it as if it were a link. There might not be an elegant way to fix that.
| | Show Home Directory Warning | `ui.showHomeDirectoryWarning` | Show a warning when running Gemini CLI in the home directory. | `true` | | ||
| | Show Compatibility Warnings | `ui.showCompatibilityWarnings` | Show warnings about terminal or OS compatibility issues. | `true` | | ||
| | Hide Tips | `ui.hideTips` | Hide helpful tips in the UI | `false` | | ||
| | Escape Pasted @ Symbols | `ui.escapePastedAtSymbols` | When enabled, @ symbols in pasted text are escaped to prevent unintended @path expansion. Disable if you intentionally paste @file references. | `false` | |
|
|
||
| - **`ui.escapePastedAtSymbols`** (boolean): | ||
| - **Description:** When enabled, @ symbols in pasted text are escaped to | ||
| prevent unintended @path expansion. Disable if you intentionally paste @file |
| requiresRestart: false, | ||
| default: false, | ||
| description: | ||
| 'When enabled, @ symbols in pasted text are escaped to prevent unintended @path expansion. Disable if you intentionally paste @file references.', |
| } | ||
| // Ensure we never accidentally interpret paste as regular input. | ||
| buffer.handleInput(key); | ||
| let sequenceToProcess = key.sequence || ''; |
There was a problem hiding this comment.
This can be simplified a lot:
if (settings.ui?.escapePastedAtSymbols) {
const escapedSequence = (key.sequence || '').replace(/(?<!\)@/g, '\@');
buffer.handleInput({ ...key, sequence: escapedSequence });
} else {
buffer.handleInput(key);
}
There was a problem hiding this comment.
Done, matches your suggested pattern.
| @@ -0,0 +1,15 @@ | ||
| /** |
There was a problem hiding this comment.
Do not create a new file for a single method. There are many files this could reasonably be added to. Too many, in fact. Review the options and pick one of them.
Also, add some tests for it.
There was a problem hiding this comment.
Moved to atCommandProcessor.ts, tests added.
| * Copyright 2025 Google LLC | ||
| * SPDX-License-Identifier: Apache-2.0 | ||
| */ | ||
|
|
There was a problem hiding this comment.
It would make sense for the escaping logic to live next to this method so we can test them together.
There was a problem hiding this comment.
Done, both live in atCommandProcessor.ts.
| segments.forEach((seg, segIdx) => { | ||
| const segLen = cpLen(seg.text); | ||
| let display = seg.text; | ||
| let display = unescapeLiteralAt(seg.text); |
There was a problem hiding this comment.
I'm worried this will lead to display bugs since the cursor position is calculated using this in cursorVisualColAbsolute()
Honestly, I think we should just show the slash in the input so users aren't confused.
There was a problem hiding this comment.
Kept \@ visible in input as suggested.
| parts.push({ | ||
| type: 'text', | ||
| content: query.substring(lastIndex, matchIndex), | ||
| content: unescapeLiteralAt(query.substring(lastIndex, matchIndex)), |
There was a problem hiding this comment.
Here and elsewhere in this class, we should only unescape if the setting is enabled. Otherwise, we will be taking out only slashes we ourselves did not put in.
There was a problem hiding this comment.
Gated behind escapePastedAtSymbols check.
160fe07 to
b010834
Compare
b010834 to
2007ae5
Compare
scidomino
left a comment
There was a problem hiding this comment.
Please look into fixing the highlighting problem in parseInputForHighlighting(). It might be hard. It might be easy. Either way I need to hear from you that you looked into it.
| } | ||
| // Ensure we never accidentally interpret paste as regular input. | ||
| buffer.handleInput(key); | ||
| if (settings.ui?.escapePastedAtSymbols) { |
There was a problem hiding this comment.
Restrict this to paste keys only:
if(key.paste && settings.ui?.escapePastedAtSymbols)
There was a problem hiding this comment.
Done, guarded with key.paste &&.
| // Ensure we never accidentally interpret paste as regular input. | ||
| buffer.handleInput(key); | ||
| if (settings.ui?.escapePastedAtSymbols) { | ||
| const escapedSequence = (key.sequence || '').replace( |
There was a problem hiding this comment.
use the escape function you wrote instead of reimplementing it.
There was a problem hiding this comment.
Done, using escapeAtSymbols().
2f50eff to
51176a3
Compare
|
Addressed both points. Also looked into |
scidomino
left a comment
There was a problem hiding this comment.
I misremembered. it's actual key.name == 'paste' or something like that. You would have noticed that had you tested your code after making the change, or even tried to build it. As it is, your PR doesn't build. Please make the change, and test it manually. Let me know when you are ready for review.
Adds an opt-in setting \ui.escapePastedAtSymbols\ (default: false) that escapes @ symbols in pasted text before they enter the input buffer, preventing emails and npm-style paths like @scope/pkg from triggering unintended @path expansion and ReadManyFiles flooding. When enabled, escaping is applied at both paste paths (Ctrl/Cmd+V and bracketed paste events). Escaped tokens are unescaped at the display layer so users see clean @ in the UI, and again before text is sent to the model via unescapeLiteralAt(). The setting is disabled by default to preserve existing behavior for users who intentionally paste @file references. Fixes google-gemini#13487
Head branch was pushed to by a user without write access
51176a3 to
008eb83
Compare
|
Build is fixed, removed |
|
Awesome! Thanks for the contribution! |
19e0b1f
Summary
Adds an opt-in setting
ui.escapePastedAtSymbols(default:false) that escapes@symbols in pasted text before they enter the input buffer, preventing emails and npm-style paths like@scope/pkgfrom triggering unintended@pathexpansion and ReadManyFiles flooding. Disabled by default to preserve existing behavior for users who intentionally paste@filereferences.Details
When enabled, escaping is applied at both paste paths (Ctrl/Cmd+V and bracketed paste events). Escaped
\@is shown as-is in the input, and unescaped back to@before the query is sent to the model. TheunescapeLiteralAt()utility correctly handles sequences like\\@without corrupting surrounding backslashes. BothescapeAtSymbols()andunescapeLiteralAt()live inatCommandProcessor.tsso they can be tested together.Related Issues
Fixes #13487
How to Validate
"ui": { "escapePastedAtSymbols": true }in~/.gemini/settings.jsonnode packages/cli/dist/index.jstest@domain.com @README.md, verify no file expansion, input showstest\@domain.com, model receives clean textfalse), verify pasted@README.mdstill expands normallyPre-Merge Checklist
npm run docs:settings)