Repository: Aider-AI/aider

Issue:
[Bug] Can't use `/read-only` if filepath contains `[]` while `/add` is working fine
### Issue

```
Readonly: AIDER-INSTRUCTION.md README.md src/lib/api/api.ts                               
> /read-only /Volumes/t7ex/Documents/oatnil/ud/ud-next-web/src/app/todolist/[id]/page.tsx 

No matches found for: 
/Volumes/t7ex/Documents/oatnil/ud/ud-next-web/src/app/todolist/[id]/page.tsx
──────────────────────────────────────────────────────────────────────────────────────────
Readonly: AIDER-INSTRUCTION.md README.md src/lib/api/api.ts                               
> /add /Volumes/t7ex/Documents/oatnil/ud/ud-next-web/src/app/todolist/[id]/page.tsx       

Added src/app/todolist/[id]/page.tsx to the chat
```

### Version and model info

version 0.84.0
executable /Users/lintao/.local/share/uv/tools/aider-chat/bin/python



Discussion:

Patch:
diff --git a/aider/commands.py b/aider/commands.py
index e21f29d9887..9bac9bab1f6 100644
--- a/aider/commands.py
+++ b/aider/commands.py
@@ -1316,12 +1316,23 @@ def cmd_read_only(self, args):
         # First collect all expanded paths
         for pattern in filenames:
             expanded_pattern = expanduser(pattern)
-            if os.path.isabs(expanded_pattern):
-                # For absolute paths, glob it
-                matches = list(glob.glob(expanded_pattern))
+            path_obj = Path(expanded_pattern)
+            is_abs = path_obj.is_absolute()
+            if not is_abs:
+                path_obj = Path(self.coder.root) / path_obj
+
+            matches = []
+            # Check for literal path existence first
+            if path_obj.exists():
+                matches = [path_obj]
             else:
-                # For relative paths and globs, use glob from the root directory
-                matches = list(Path(self.coder.root).glob(expanded_pattern))
+                # If literal path doesn't exist, try globbing
+                if is_abs:
+                    # For absolute paths, glob it
+                    matches = [Path(p) for p in glob.glob(expanded_pattern)]
+                else:
+                    # For relative paths and globs, use glob from the root directory
+                    matches = list(Path(self.coder.root).glob(expanded_pattern))
 
             if not matches:
                 self.io.tool_error(f"No matches found for: {pattern}")


Test patch:
diff --git a/tests/basic/test_commands.py b/tests/basic/test_commands.py
index d187ee5210e..47b8832dc42 100644
--- a/tests/basic/test_commands.py
+++ b/tests/basic/test_commands.py
@@ -1621,6 +1621,33 @@ def test_cmd_read_only_with_tilde_path(self):
                 # Clean up: remove the test file from the home directory
                 test_file.unlink()
 
+    # pytest tests/basic/test_commands.py  -k test_cmd_read_only_with_square_brackets
+    def test_cmd_read_only_with_square_brackets(self):
+        with GitTemporaryDirectory() as repo_dir:
+            io = InputOutput(pretty=False, fancy_input=False, yes=False)
+            coder = Coder.create(self.GPT35, None, io)
+            commands = Commands(io, coder)
+
+            # Create test layout
+            test_dir = Path(repo_dir) / "[id]"
+            test_dir.mkdir()
+            test_file = Path(repo_dir) / "[id]" / "page.tsx"
+            test_file.write_text("Test file")
+
+            # Test the /read-only command
+            commands.cmd_read_only("[id]/page.tsx")
+
+            # Check if test file was added to abs_read_only_fnames
+            self.assertTrue(
+                any(os.path.samefile(str(test_file), fname) for fname in coder.abs_read_only_fnames)
+            )
+
+            # Test dropping all read-only files
+            commands.cmd_drop("[id]/page.tsx")
+
+            # Check if all files were removed from abs_read_only_fnames
+            self.assertEqual(len(coder.abs_read_only_fnames), 0)
+
     def test_cmd_diff(self):
         with GitTemporaryDirectory() as repo_dir:
             repo = git.Repo(repo_dir)