* Respect agent ignore conventions through configurable filtering Constraint: Issue #1746 requests .agentignore/.aiignore compatibility and maintainer feedback asks for a custom ignore-file configuration path. Rejected: Hardcode only .agentignore/.aiignore | would not satisfy the maintainer's configurable-ignore direction. Confidence: high Scope-risk: moderate Directive: Keep .qwenignore always included when respectQwenIgnore is enabled; route extra filenames through customIgnoreFiles. Tested: Core targeted vitest suite; CLI targeted vitest plus config integration; npm run build && npm run typecheck; targeted eslint; git diff --check. Not-tested: Full repository test suite and model-driven end-to-end CLI integration tests. * fix(cli): keep custom ignore settings type-safe and honest The schema default otherwise narrows customIgnoreFiles to the built-in tuple and the UI text implies additive behavior that the implementation does not provide. Constraint: Address wenshao's two review comments without changing replacement semantics. Rejected: Merge user values with defaults | Larger behavior change was not requested for this follow-up. Confidence: high Scope-risk: narrow Directive: Preserve replacement semantics unless a future change intentionally updates config merging and docs together. Tested: cd packages/cli && npx vitest run src/config/config.test.ts src/config/settingsSchema.test.ts; npm run build; npm run typecheck; npm run lint; git diff --check Not-tested: Full integration suite. * fix(core): keep custom ignore settings consistent Ensure review-sensitive file discovery paths, grep ignore resolution, and user-facing ignore names preserve the configured custom ignore behavior instead of falling back to defaults or search-directory-local files. Constraint: PR #4653 review feedback requires customIgnoreFiles to behave consistently across secondary discovery and grep paths. Rejected: Merge custom ignore files with defaults | The current PR documents replacement semantics and prior review feedback accepted that contract. Confidence: high Scope-risk: narrow Directive: Keep .qwenignore always included, but treat customIgnoreFiles as the replacement list for additional AI ignore files. Tested: packages/core targeted vitest for qwenIgnoreParser, fileDiscoveryService, ripGrep, config, read-file; packages/cli config vitest; targeted workspaceFileSystem custom-ignore vitest; Prettier check; ESLint on touched files; git diff --check. Not-tested: Full workspace typecheck/build because current branch fails before this change on environmentContext.test.ts syntax and unrelated CLI type errors; full workspaceFileSystem suite on Windows because existing symlink tests fail with EPERM. * test(core): cover subagent custom ignore inheritance Keep the in-process backend tests aligned with the new per-agent file filtering contract so CI catches missing custom-ignore propagation. Constraint: PR review fixes require subagents to inherit parent custom ignore settings. Confidence: high Scope-risk: narrow Tested: npx vitest run src/agents/backends/InProcessBackend.test.ts Tested: npx vitest run src/utils/qwenIgnoreParser.test.ts src/services/fileDiscoveryService.test.ts src/tools/ripGrep.test.ts src/config/config.test.ts src/tools/read-file.test.ts src/agents/backends/InProcessBackend.test.ts src/tools/agent/agent.test.ts Tested: npx vitest run src/serve/fs/workspaceFileSystem.test.ts -t "uses configured custom ignore files" Tested: npx prettier --check packages/core/src/agents/backends/InProcessBackend.test.ts Tested: npx eslint packages/core/src/agents/backends/InProcessBackend.test.ts Tested: git diff --check Not-tested: full npm run typecheck remains blocked by existing branch-wide TypeScript errors outside this test change * test(core): fix subagent custom ignore test typing Keep the custom-ignore regression test type-checkable under the repo build, where createMockConfig is intentionally cast to never. Constraint: CI runs package build during dependency installation and type-checks test files. Confidence: high Scope-risk: narrow Tested: npx vitest run src/agents/backends/InProcessBackend.test.ts Tested: npx prettier --check packages/core/src/agents/backends/InProcessBackend.test.ts Tested: npx eslint packages/core/src/agents/backends/InProcessBackend.test.ts Tested: git diff --check Not-tested: npm run build --workspace=packages/core is still blocked locally by src/utils/environmentContext.test.ts(599,1): error TS1005: '}' expected * test(core): align notebook ignore message expectation Keep notebook ignore validation tests in sync with the comma-separated ignore file display used by the review fix. Constraint: PR review fixes changed ignore file display text from slash-separated to comma-separated. Confidence: high Scope-risk: narrow Tested: npx vitest run src/tools/notebook-edit.test.ts Tested: npx vitest run src/utils/qwenIgnoreParser.test.ts src/services/fileDiscoveryService.test.ts src/tools/ripGrep.test.ts src/tools/read-file.test.ts src/tools/notebook-edit.test.ts src/config/config.test.ts src/agents/backends/InProcessBackend.test.ts src/tools/agent/agent.test.ts Tested: npx prettier --check packages/core/src/tools/notebook-edit.test.ts Tested: npx eslint packages/core/src/tools/notebook-edit.test.ts Tested: git diff --check Not-tested: full npm run build --workspace=packages/core remains blocked locally by src/utils/environmentContext.test.ts(599,1): error TS1005: '}' expected * fix(core): keep ripgrep ignore roots canonical Constraint: PR #4653 review 4453010590 requested absolute ignore-root fallback behavior. Rejected: Broader customIgnoreFiles semantic changes | outside the review scope and replacement semantics stay unchanged. Confidence: high Scope-risk: narrow Directive: Keep customIgnoreFiles as replacement for compatibility defaults while always including .qwenignore. Tested: cd packages/core && npx vitest run src/tools/ripGrep.test.ts; npx prettier --check src/tools/ripGrep.ts src/tools/ripGrep.test.ts; git diff --check Not-tested: npm run typecheck --workspace=packages/core fails on existing src/utils/environmentContext.test.ts parse error. * test(core): align ripgrep ignore path expectation Constraint: macOS canonicalizes temporary paths through /private/var after process.chdir. Rejected: Changing ripgrep ignore-root behavior | implementation already uses path.resolve correctly. Confidence: high Scope-risk: narrow Directive: Keep the regression test tied to path.resolve behavior rather than raw temp-dir spelling. Tested: cd packages/core && npx vitest run src/tools/ripGrep.test.ts; npx prettier --check src/tools/ripGrep.test.ts src/tools/ripGrep.ts; git diff --check Not-tested: Full CI rerun is remote-only after push. * fix(core): make custom ignore feedback actionable Review feedback for PR #4653 showed users could not tell which ignore file blocked a path, and worktree isolation lacked coverage for inherited custom ignore files. This also closes the existing environmentContext.test describe block so build can progress to the current unrelated converter type blocker. Constraint: PR #4653 keeps replacement semantics for customIgnoreFiles and always includes .qwenignore. Rejected: Exposing matching pattern text | merged ignore and negation semantics can make pattern-level attribution misleading. Confidence: high Scope-risk: moderate Directive: Keep customIgnoreFiles as replacement semantics unless the config contract changes deliberately. Tested: core targeted Vitest suite; ripGrep regression test; cli settingsSchema test; Prettier check. Not-tested: npm run build && npm run typecheck blocked by existing FinishReason type errors in converter.ts. * fix(core): isolate qwen ignore sources * test(cli): mock qwen ignore defaults in acp test * test(scripts): make dev launcher test path portable * fix(core): prevent ripgrep ignore negation bypass * fix(core): post-filter ripgrep qwenignore matches * fix(core): preserve ignore negations in grep - Preserve non-.qwenignore negation semantics for grep searches - Skip workspace-external ignore-file discovery - Add coverage for ignore diagnostics and addSource behavior * test(core): update yaml nested parser expectations * chore: remove unrelated formatting churn
17 KiB
Qwen Code file system tools
Qwen Code provides a comprehensive suite of tools for interacting with the local file system. These tools allow the model to read from, write to, list, search, and modify files and directories, all under your control and typically with confirmation for sensitive operations.
Note: All file system tools operate within a rootDirectory (usually the current working directory where you launched the CLI) for security. Paths that you provide to these tools are generally expected to be absolute or are resolved relative to this root directory.
1. list_directory (ListFiles)
list_directory lists the names of files and subdirectories directly within a specified directory path. It can optionally ignore entries matching provided glob patterns.
- Tool name:
list_directory - Display name: ListFiles
- File:
ls.ts - Parameters:
path(string, required): The absolute path to the directory to list.ignore(array of strings, optional): A list of glob patterns to exclude from the listing (e.g.,["*.log", ".git"]).respect_git_ignore(boolean, optional): Whether to respect.gitignorepatterns when listing files. Defaults totrue.
- Behavior:
- Returns a list of file and directory names.
- Indicates whether each entry is a directory.
- Sorts entries with directories first, then alphabetically.
- Output (
llmContent): A string like:Directory listing for /path/to/your/folder:\n[DIR] subfolder1\nfile1.txt\nfile2.png - Confirmation: No.
2. read_file (ReadFile)
read_file reads and returns the content of a specified file. This tool handles text files and media files (images, PDFs, audio, video) whose modality is supported by the current model. For text files, it can read specific line ranges. Media files whose modality is not supported by the current model are rejected with a helpful error message. Other binary file types are generally skipped.
- Tool name:
read_file - Display name: ReadFile
- File:
read-file.ts - Parameters:
path(string, required): The absolute path to the file to read.offset(number, optional): For text files, the 0-based line number to start reading from. Requireslimitto be set.limit(number, optional): For text files, the maximum number of lines to read. If omitted, reads a default maximum (e.g., 2000 lines) or the entire file if feasible.
- Behavior:
- For text files: Returns the content. If
offsetandlimitare used, returns only that slice of lines. Indicates if content was truncated due to line limits or line length limits. - For media files (images, PDFs, audio, video): If the current model supports the file's modality, returns the file content as a base64-encoded
inlineDataobject. If the model does not support the modality, returns an error message with guidance (e.g., suggesting skills or external tools). - For other binary files: Attempts to identify and skip them, returning a message indicating it's a generic binary file.
- For text files: Returns the content. If
- Output: (
llmContent):- For text files: The file content, potentially prefixed with a truncation message (e.g.,
[File content truncated: showing lines 1-100 of 500 total lines...]\nActual file content...). - For supported media files: An object containing
inlineDatawithmimeTypeand base64data(e.g.,{ inlineData: { mimeType: 'image/png', data: 'base64encodedstring' } }). - For unsupported media files: An error message string explaining that the current model does not support this modality, with suggestions for alternatives.
- For other binary files: A message like
Cannot display content of binary file: /path/to/data.bin.
- For text files: The file content, potentially prefixed with a truncation message (e.g.,
- Confirmation: No.
Jupyter notebook reads
For Jupyter notebooks (.ipynb), read_file parses the notebook JSON and returns a structured, model-readable notebook view instead of raw JSON. The rendered output includes the notebook language, ordered cells, cell IDs, source, and summarized outputs.
Notebook cells can then be edited with notebook_edit. The model should use the cell IDs shown by read_file when targeting a cell.
offset and limit are not supported for .ipynb files. Notebook reads are treated as structured full-file reads; if the rendered notebook output is internally truncated because it is too large, notebook_edit will reject cell-level edits and ask you to reduce outputs or split the notebook before editing.
3. notebook_edit (NotebookEdit)
notebook_edit edits Jupyter notebook (.ipynb) files safely at the cell level. Use it instead of edit or write_file when changing notebook cells.
- Tool name:
notebook_edit - Display name: NotebookEdit
- File:
notebook-edit.ts - Parameters:
notebook_path(string, required): The absolute path to the.ipynbfile.cell_id(string, optional): The target cell ID shown byread_file. Required forreplaceanddelete. Forinsert, the new cell is inserted after this cell; if omitted, the new cell is inserted at the beginning.new_source(string, optional): The new cell source forreplaceandinsert. Not required fordelete.cell_type(codeormarkdown, optional): The cell type for inserted cells, or the target type when replacing a cell.edit_mode(replace,insert, ordelete, optional): The edit operation. Defaults toreplace.
- Behavior:
- Requires the notebook to have been read first with
read_filein the current session. - Targets cells using the IDs rendered by
read_file, including real notebook cell IDs and displayedcell-Nfallback IDs. - Rejects ambiguous rendered cell IDs instead of guessing.
- For code cells, clears stale outputs and resets
execution_countwhen source changes. - Preserves notebook JSON formatting, line endings, encoding, and BOM where possible.
- Invalidates the prior-read state after structural edits when displayed fallback IDs can shift, so the next notebook edit requires a fresh
read_file.
- Requires the notebook to have been read first with
- Output (
llmContent): A success message describing the edited notebook cell and, for non-delete operations, the updated source. - Confirmation: Yes. Shows a notebook JSON diff and asks for user approval before writing, unless the current permission mode or rules auto-approve edit tools.
notebook_edit examples
Replace a code cell:
notebook_edit(
notebook_path="/path/to/analysis.ipynb",
cell_id="load-data",
new_source="result = 41 + 1\nprint(result)"
)
Insert a markdown cell after an existing cell:
notebook_edit(
notebook_path="/path/to/analysis.ipynb",
edit_mode="insert",
cell_id="summary",
cell_type="markdown",
new_source="## Findings\n\nThe cleaned data is ready for modeling."
)
Delete a cell:
notebook_edit(
notebook_path="/path/to/analysis.ipynb",
edit_mode="delete",
cell_id="old-experiment"
)
4. write_file (WriteFile)
write_file writes content to a specified file. If the file exists, it will be overwritten. If the file doesn't exist, it (and any necessary parent directories) will be created.
- Tool name:
write_file - Display name: WriteFile
- File:
write-file.ts - Parameters:
file_path(string, required): The absolute path to the file to write to.content(string, required): The content to write into the file.
- Behavior:
- Writes the provided
contentto thefile_path. - Does not write raw Jupyter notebook JSON. Use
notebook_editfor.ipynbcell edits. - Creates parent directories if they don't exist.
- Writes the provided
- Output (
llmContent): A success message, e.g.,Successfully overwrote file: /path/to/your/file.txtorSuccessfully created and wrote to new file: /path/to/new/file.txt. - Confirmation: Yes. Shows a diff of changes and asks for user approval before writing.
5. glob (Glob)
glob finds files matching specific glob patterns (e.g., src/**/*.ts, *.md), returning absolute paths sorted by modification time (newest first).
- Tool name:
glob - Display name: Glob
- File:
glob.ts - Parameters:
pattern(string, required): The glob pattern to match against (e.g.,"*.py","src/**/*.js").path(string, optional): The directory to search in. If not specified, the current working directory will be used.
- Behavior:
- Searches for files matching the glob pattern within the specified directory.
- Returns a list of absolute paths, sorted with the most recently modified files first.
- Respects .gitignore, .qwenignore, and configured custom Qwen ignore files by default.
- Limits results to 100 files to prevent context overflow.
- Output (
llmContent): A message like:Found 5 file(s) matching "*.ts" within /path/to/search/dir, sorted by modification time (newest first):\n---\n/path/to/file1.ts\n/path/to/subdir/file2.ts\n---\n[95 files truncated] ... - Confirmation: No.
6. grep_search (Grep)
grep_search searches for a regular expression pattern within the content of files in a specified directory. Can filter files by a glob pattern. Returns the lines containing matches, along with their file paths and line numbers.
-
Tool name:
grep_search -
Display name: Grep
-
File:
grep.ts(withripGrep.tsas fallback) -
Parameters:
pattern(string, required): The regular expression pattern to search for in file contents (e.g.,"function\\s+myFunction","log.*Error").path(string, optional): File or directory to search in. Defaults to current working directory.glob(string, optional): Glob pattern to filter files (e.g."*.js","src/**/*.{ts,tsx}").limit(integer, optional): Limit output to first N matching lines. Must be a positive integer. Optional - shows all matches if not specified.
-
Behavior:
- Uses ripgrep for fast search when available; otherwise falls back to a JavaScript-based search implementation.
- Returns matching lines with file paths and line numbers.
- Case-insensitive by default.
- Respects .gitignore, .qwenignore, and configured custom Qwen ignore files.
- Limits output to prevent context overflow.
-
Output (
llmContent): A formatted string of matches, e.g.:Found 3 matches for pattern "myFunction" in path "." (filter: "*.ts"): --- src/utils.ts:15:export function myFunction() { src/utils.ts:22: myFunction.call(); src/index.ts:5:import { myFunction } from './utils'; --- [0 lines truncated] ... -
Confirmation: No.
grep_search examples
Search for a pattern with default result limiting:
grep_search(pattern="function\\s+myFunction", path="src")
Search for a pattern with custom result limiting:
grep_search(pattern="function", path="src", limit=50)
Search for a pattern with file filtering and custom result limiting:
grep_search(pattern="function", glob="*.js", limit=10)
7. edit (Edit)
edit replaces text within a file. By default it requires old_string to match a single unique location; set replace_all to true when you intentionally want to change every occurrence. This tool is designed for precise, targeted changes and requires significant context around the old_string to ensure it modifies the correct location.
-
Tool name:
edit -
Display name: Edit
-
File:
edit.ts -
Parameters:
-
file_path(string, required): The absolute path to the file to modify. -
old_string(string, required): The exact literal text to replace.CRITICAL: This string must uniquely identify the single instance to change. It should include sufficient context around the target text, matching whitespace and indentation precisely. If
old_stringis empty, the tool attempts to create a new file atfile_pathwithnew_stringas content. -
new_string(string, required): The exact literal text to replaceold_stringwith. -
replace_all(boolean, optional): Replace all occurrences ofold_string. Defaults tofalse.
-
-
Behavior:
- Does not edit raw Jupyter notebook JSON. Use
notebook_editfor.ipynbcell edits. - If
old_stringis empty andfile_pathdoes not exist, creates a new file withnew_stringas content. - If
old_stringis provided, it reads thefile_pathand attempts to find exactly one occurrence unlessreplace_allis true. - If the match is unique (or
replace_allis true), it replaces the text withnew_string. - Enhanced Reliability (Multi-Stage Edit Correction): To significantly improve the success rate of edits, especially when the model-provided
old_stringmight not be perfectly precise, the tool incorporates a multi-stage edit correction mechanism.- If the initial
old_stringisn't found or matches multiple locations, the tool can leverage the Qwen model to iteratively refineold_string(and potentiallynew_string). - This self-correction process attempts to identify the unique segment the model intended to modify, making the
editoperation more robust even with slightly imperfect initial context.
- If the initial
- Does not edit raw Jupyter notebook JSON. Use
-
Failure conditions: Despite the correction mechanism, the tool will fail if:
file_pathis not absolute or is outside the root directory.old_stringis not empty, but thefile_pathdoes not exist.old_stringis empty, but thefile_pathalready exists.old_stringis not found in the file after attempts to correct it.old_stringis found multiple times,replace_allis false, and the self-correction mechanism cannot resolve it to a single, unambiguous match.
-
Output (
llmContent):- On success:
Successfully modified file: /path/to/file.txt (1 replacements).orCreated new file: /path/to/new_file.txt with provided content. - On failure: An error message explaining the reason (e.g.,
Failed to edit, 0 occurrences found...,Failed to edit because the text matches multiple locations...).
- On success:
-
Confirmation: Yes. Shows a diff of the proposed changes and asks for user approval before writing to the file.
File encoding and platform-specific behavior
Encoding detection and preservation
When reading files, Qwen Code detects the file's encoding using a multi-step strategy:
- UTF-8 — tried first (most modern tooling outputs UTF-8)
- chardet — statistical detection for non-UTF-8 content
- System encoding — falls back to the OS code page (Windows
chcp/ UnixLANG)
Both write_file and edit preserve the original encoding and BOM (byte order mark) of existing files. If a file was read as GBK with a UTF-8 BOM, it will be written back the same way.
Configuring default encoding for new files
The defaultFileEncoding setting controls encoding for newly created files (not edits to existing files):
| Value | Behavior |
|---|---|
| (not set) | UTF-8 without BOM, with automatic platform-specific adjustments (see below) |
utf-8 |
UTF-8 without BOM, no automatic adjustments |
utf-8-bom |
UTF-8 with BOM for all new files |
Set it in .qwen/settings.json or ~/.qwen/settings.json:
{
"general": {
"defaultFileEncoding": "utf-8-bom"
}
}
Windows: CRLF for batch files
On Windows, .bat and .cmd files are automatically written with CRLF (\r\n) line endings. This is required because cmd.exe uses CRLF as its line delimiter — LF-only endings can break multi-line if/else, goto labels, and for loops. This applies regardless of encoding settings and only on Windows.
Windows: UTF-8 BOM for PowerShell scripts
On Windows with a non-UTF-8 system code page (e.g. GBK/cp936, Big5/cp950, Shift_JIS/cp932), newly created .ps1 files are automatically written with a UTF-8 BOM. This is necessary because Windows PowerShell 5.1 (the version built into Windows 10/11) reads BOM-less scripts using the system's ANSI code page. Without a BOM, any non-ASCII characters in the script will be misinterpreted.
This automatic BOM only applies when:
- The platform is Windows
- The system code page is not UTF-8 (not code page 65001)
- The file is a new
.ps1file (existing files keep their original encoding) - The user has not explicitly set
defaultFileEncodingin settings
PowerShell 7+ (pwsh) defaults to UTF-8 and handles BOM transparently, so the BOM is harmless there.
If you explicitly set defaultFileEncoding to "utf-8", the automatic BOM is disabled — this is an intentional escape hatch for repositories or tooling that reject BOMs.
Summary
| File type | Platform | Automatic behavior |
|---|---|---|
.bat, .cmd |
Windows | CRLF line endings |
.ps1 |
Windows (non-UTF-8 code page) | UTF-8 BOM on new files |
| All others | All | UTF-8 without BOM (default) |
These file system tools provide a foundation for Qwen Code to understand and interact with your local project context.