Skip to content

fix(zsh): escape parentheses and brackets in completion descriptions#559

Merged
jdx merged 1 commit intomainfrom
fix/zsh-escape-parens-in-completions
Mar 23, 2026
Merged

fix(zsh): escape parentheses and brackets in completion descriptions#559
jdx merged 1 commit intomainfrom
fix/zsh-escape-parens-in-completions

Conversation

@jdx
Copy link
Copy Markdown
Owner

@jdx jdx commented Mar 23, 2026

Summary

  • Escape (, ), [, ] in zsh completion output to prevent _describe from interpreting them as glob qualifiers/character classes
  • Extract a zsh_escape() helper that handles all zsh-special characters (colons, parens, brackets)
  • Add test fixture and integration test verifying the escaping

Fixes #558

Test plan

  • New test complete_word_zsh_escapes_parens_and_brackets verifies escaped output
  • Verified test fails without the fix (parentheses/brackets passed through unescaped)
  • Full test suite passes
  • Clippy clean

🤖 Generated with Claude Code


Note

Low Risk
Low risk: only adjusts zsh completion output formatting by adding additional escaping, with coverage via a new integration test.

Overview
Fixes zsh completion output for _describe by escaping additional special characters in name:description entries. The previous inline colon escaping is replaced with a shared zsh_escape() helper that also escapes (, ), [ and ] to prevent glob/character-class interpretation.

Adds an integration test plus a new example spec to verify zsh output is correctly escaped for descriptions containing parentheses/brackets (and colons).

Written by Cursor Bugbot for commit f165847. This will update automatically on new commits. Configure here.

zsh's `_describe` function interprets parentheses as glob qualifiers
and brackets as character classes, causing errors like "unknown file
attribute" and "unknown sort specifier" when completion descriptions
contain these characters.

Fixes #558

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
@gemini-code-assist
Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, 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 addresses an issue where Zsh's _describe function incorrectly interpreted special characters like parentheses and brackets in completion descriptions, leading to unexpected behavior. The changes introduce robust escaping for these characters, along with colons, ensuring that Zsh completion output is correctly parsed. A new helper function centralizes this escaping logic, and comprehensive tests have been added to validate the fix.

Highlights

  • Zsh Completion Escaping: Implemented escaping for parentheses and brackets in Zsh completion descriptions to prevent misinterpretation as glob qualifiers or character classes by _describe.
  • New Helper Function: Introduced a dedicated zsh_escape() helper function to centralize the escaping logic for all Zsh-specific special characters, including colons, parentheses, and brackets.
  • Enhanced Testing: Added a new test fixture and an integration test to specifically verify the correct escaping behavior for these characters in Zsh completion output.
Using Gemini Code Assist

The 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 /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

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 .gemini/ folder in the base of the repository. Detailed instructions can be found here.

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.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@codecov
Copy link
Copy Markdown

codecov Bot commented Mar 23, 2026

Codecov Report

❌ Patch coverage is 30.00000% with 7 lines in your changes missing coverage. Please review.
✅ Project coverage is 70.07%. Comparing base (1e81f1b) to head (f165847).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
cli/src/cli/complete_word.rs 30.00% 1 Missing and 6 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #559      +/-   ##
==========================================
+ Coverage   70.04%   70.07%   +0.03%     
==========================================
  Files          48       48              
  Lines        7080     7088       +8     
  Branches     7080     7088       +8     
==========================================
+ Hits         4959     4967       +8     
- Misses       1322     1323       +1     
+ Partials      799      798       -1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Mar 23, 2026

Greptile Summary

This PR fixes a zsh completion bug where unescaped (, ), [, and ] characters in completion descriptions caused _describe to misinterpret them as glob qualifiers or character classes. The fix extracts a zsh_escape() helper that escapes all four characters (in addition to the pre-existing colon escaping) and applies it to both the completion value and its description before formatting the value:description output line.

  • Root cause correctly identified: zsh's _describe uses parentheses for tag syntax (value(tag):description) and brackets for character classes, so raw (, ), [, ] in either field corrupt the completion output.
  • Refactor is clean: zsh_escape() consolidates all three escape rules (colon, parens, brackets) in one well-documented helper, replacing two inline replace calls.
  • Data flow is correct: complete_arg parses the run script output and unescapes \: before returning plain (value, description) tuples; zsh_escape then re-escapes for _describe — no double-escaping occurs.
  • Test coverage is good: The new fixture (parens-in-descriptions.usage.kdl) exercises both colons-in-values and parens/brackets-in-descriptions in a single zsh-shell integration test.
  • Minor pre-existing gap: When any_descriptions is false, completion values printed to _describe are still not escaped for parens/brackets. This is out of scope for this PR (which targets issue zsh completion breaks when task description contains parentheses (v3.x regression) #558) but could be a follow-up if values themselves ever contain these characters.

Confidence Score: 5/5

  • Safe to merge — targeted one-function fix with a matching integration test, no regressions introduced.
  • The change is minimal and well-scoped: a new private helper function, applied only in the already-existing zsh+descriptions code path. The data-flow trace confirms no double-escaping. The test fixture and assertion are consistent with the expected output. Existing tests continue to pass (clippy clean, full suite green per PR description).
  • No files require special attention.

Important Files Changed

Filename Overview
cli/src/cli/complete_word.rs Adds zsh_escape() helper that escapes (, ), [, ] in addition to the existing : escaping; helper is applied to both the completion value and description in the zsh any_descriptions branch.
cli/tests/complete_word.rs Adds complete_word_zsh_escapes_parens_and_brackets integration test that verifies escaped output for both colons in values and parentheses/brackets in descriptions.
examples/parens-in-descriptions.usage.kdl New fixture with a run command whose completions include colons in values (connect\:server, test\:unit) and parentheses/brackets in descriptions, matching the test expectations.

Sequence Diagram

sequenceDiagram
    participant Shell as zsh completion script
    participant CW as complete-word (--shell zsh)
    participant Script as run script (sh -c)

    Shell->>CW: complete-word --shell zsh -f spec -- words
    CW->>Script: sh -c "echo connect\\:server:Connect server (Hot Reload)\n..."
    Script-->>CW: connect\:server:Connect server (Hot Reload)\ntest\:unit:Run tests [fast]\nbuild:Build project
    Note over CW: parse: split on first unescaped colon,<br/>unescape \\: → : in value & description
    Note over CW: zsh_escape(value) → escape :, (, ), [, ]<br/>zsh_escape(description) → same
    CW-->>Shell: connect\:server:Connect server \(Hot Reload\)\ntest\:unit:Run tests \[fast\]\nbuild:Build project
    Shell->>Shell: completions+=("$line") for each line
    Shell->>Shell: _describe 'completions' completions -S ''
Loading

Reviews (1): Last reviewed commit: "fix(zsh): escape parentheses and bracket..." | Re-trigger Greptile

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a new zsh_escape function to correctly handle special characters like colons, parentheses, and brackets in zsh completion output, addressing an issue where these characters were misinterpreted as glob qualifiers or character classes. A new test case and example file have been added to validate this functionality. A performance improvement opportunity was identified for the zsh_escape function, suggesting a single-pass iteration instead of chained replace calls to reduce string allocations.

Comment on lines +363 to +367
s.replace(':', "\\:")
.replace('(', "\\(")
.replace(')', "\\)")
.replace('[', "\\[")
.replace(']', "\\]")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

While this implementation is correct, chaining replace calls can be inefficient as it may create intermediate String allocations and perform multiple passes over the string. A more performant approach would be to iterate over the string once and build the escaped string in a single pass. This avoids the overhead of multiple allocations and iterations, especially if the input strings can be long.

Suggested change
s.replace(':', "\\:")
.replace('(', "\\(")
.replace(')', "\\)")
.replace('[', "\\[")
.replace(']', "\\]")
let mut escaped = String::with_capacity(s.len());
for c in s.chars() {
match c {
':' | '(' | ')' | '[' | ']' => {
escaped.push('\\');
escaped.push(c);
}
_ => escaped.push(c),
}
}
escaped

@jdx jdx merged commit 1f26b3d into main Mar 23, 2026
12 checks passed
@jdx jdx deleted the fix/zsh-escape-parens-in-completions branch March 23, 2026 18:06
jdx pushed a commit that referenced this pull request Mar 23, 2026
### 🚀 Features

- Support env-backed choices with `choices env=...` by
[@mustafa0x](https://github.com/mustafa0x) in
[#548](#548)

### 🐛 Bug Fixes

- **(zsh)** escape parentheses and brackets in completion descriptions
by [@jdx](https://github.com/jdx) in
[#559](#559)

### New Contributors

- @mustafa0x made their first contribution in
[#548](#548)
tmeijn pushed a commit to tmeijn/dotfiles that referenced this pull request Apr 2, 2026
⚠️ **CAUTION: this is a major update, indicating a breaking change!** ⚠️

This MR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [usage](https://github.com/jdx/usage) | major | `2.18.2` → `3.2.0` |

MR created with the help of [el-capitano/tools/renovate-bot](https://gitlab.com/el-capitano/tools/renovate-bot).

**Proposed changes to behavior should be submitted there as MRs.**

---

### Release Notes

<details>
<summary>jdx/usage (usage)</summary>

### [`v3.2.0`](https://github.com/jdx/usage/blob/HEAD/CHANGELOG.md#320---2026-03-23)

[Compare Source](jdx/usage@v3.1.0...v3.2.0)

##### 🚀 Features

- Support env-backed choices with `choices env=...` by [@&#8203;mustafa0x](https://github.com/mustafa0x) in [#&#8203;548](jdx/usage#548)

##### 🐛 Bug Fixes

- **(zsh)** escape parentheses and brackets in completion descriptions by [@&#8203;jdx](https://github.com/jdx) in [#&#8203;559](jdx/usage#559)

##### New Contributors

- [@&#8203;mustafa0x](https://github.com/mustafa0x) made their first contribution in [#&#8203;548](jdx/usage#548)

### [`v3.1.0`](https://github.com/jdx/usage/blob/HEAD/CHANGELOG.md#310---2026-03-22)

[Compare Source](jdx/usage@v3.0.0...v3.1.0)

##### 🚀 Features

- **(cli)** render all doc-related fields in --help output by [@&#8203;jdx](https://github.com/jdx) in [#&#8203;554](jdx/usage#554)
- **(cli)** support reading spec from stdin via --file - by [@&#8203;jdx](https://github.com/jdx) in [#&#8203;555](jdx/usage#555)

##### 🐛 Bug Fixes

- **(zsh)** remove trailing space from completions and add directory slash by [@&#8203;jdx](https://github.com/jdx) in [#&#8203;556](jdx/usage#556)
- use field assignment for non-exhaustive Spec in benchmarks by [@&#8203;jdx](https://github.com/jdx) in [#&#8203;552](jdx/usage#552)

##### 📦️ Dependency Updates

- update apple-actions/import-codesign-certs digest to [`fe74d46`](jdx/usage@fe74d46) by [@&#8203;renovate\[bot\]](https://github.com/renovate\[bot]) in [#&#8203;550](jdx/usage#550)
- update codecov/codecov-action digest to [`1af5884`](jdx/usage@1af5884) by [@&#8203;renovate\[bot\]](https://github.com/renovate\[bot]) in [#&#8203;551](jdx/usage#551)
- lock file maintenance by [@&#8203;renovate\[bot\]](https://github.com/renovate\[bot]) in [#&#8203;547](jdx/usage#547)

### [`v3.0.0`](https://github.com/jdx/usage/blob/HEAD/CHANGELOG.md#300---2026-03-13)

[Compare Source](jdx/usage@v2.18.2...v3.0.0)

##### 🚀 Features

- **(spec)** **breaking** add support for license, before/after help metadata by [@&#8203;jdx](https://github.com/jdx) in [#&#8203;542](jdx/usage#542)

##### 🐛 Bug Fixes

- **(cobra)** escape newlines, tabs, and carriage returns in kdlQuoteAlways by [@&#8203;thecodesmith](https://github.com/thecodesmith) in [#&#8203;539](jdx/usage#539)
- bump major version for breaking changes in release automation by [@&#8203;jdx](https://github.com/jdx) in [#&#8203;544](jdx/usage#544)
- add custom\_major\_increment\_regex for breaking change detection by [@&#8203;jdx](https://github.com/jdx) in [#&#8203;545](jdx/usage#545)
- handle all breaking change commit formats in major bump regex by [@&#8203;jdx](https://github.com/jdx) in [27e1ab1](jdx/usage@27e1ab1)
- normalize breaking change commit format in preprocessor by [@&#8203;jdx](https://github.com/jdx) in [aa72b92](jdx/usage@aa72b92)

##### 📚 Documentation

- add argparse-usage integration by [@&#8203;jdx](https://github.com/jdx) in [#&#8203;531](jdx/usage#531)
- mark KDL code blocks as KDL and use correct inline-comment `//` by [@&#8203;muzimuzhi](https://github.com/muzimuzhi) in [#&#8203;536](jdx/usage#536)
- fix include syntax to match implementation by [@&#8203;jdx](https://github.com/jdx) in [#&#8203;540](jdx/usage#540)
- consolidate integration list to single source by [@&#8203;jdx](https://github.com/jdx) in [#&#8203;541](jdx/usage#541)
- fix link to integrations by [@&#8203;muzimuzhi](https://github.com/muzimuzhi) in [#&#8203;543](jdx/usage#543)

##### 🛡️ Security

- **(deps)** update dependency eslint to v10 by [@&#8203;renovate\[bot\]](https://github.com/renovate\[bot]) in [#&#8203;526](jdx/usage#526)

##### 🔍 Other Changes

- Added an integration with ruby's OptionParser by [@&#8203;packrat386](https://github.com/packrat386) in [#&#8203;533](jdx/usage#533)

##### 📦️ Dependency Updates

- update actions/setup-node digest to [`53b8394`](jdx/usage@53b8394) by [@&#8203;renovate\[bot\]](https://github.com/renovate\[bot]) in [#&#8203;525](jdx/usage#525)
- update jdx/mise-action action to v3 by [@&#8203;renovate\[bot\]](https://github.com/renovate\[bot]) in [#&#8203;528](jdx/usage#528)
- update rust crate roff to v1 by [@&#8203;renovate\[bot\]](https://github.com/renovate\[bot]) in [#&#8203;529](jdx/usage#529)

##### New Contributors

- [@&#8203;thecodesmith](https://github.com/thecodesmith) made their first contribution in [#&#8203;539](jdx/usage#539)
- [@&#8203;packrat386](https://github.com/packrat386) made their first contribution in [#&#8203;533](jdx/usage#533)

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever MR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this MR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this MR, check this box

---

This MR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4xMDIuMTAiLCJ1cGRhdGVkSW5WZXIiOiI0My4xMDIuMTAiLCJ0YXJnZXRCcmFuY2giOiJtYWluIiwibGFiZWxzIjpbIlJlbm92YXRlIEJvdCIsImF1dG9tYXRpb246Ym90LWF1dGhvcmVkIiwiZGVwZW5kZW5jeS10eXBlOjptYWpvciJdfQ==-->
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

zsh completion breaks when task description contains parentheses (v3.x regression)

1 participant