microsoft / microsoft/vscode

Cannot read properties of undefined (reading 'length') when built-in search tool returns 0 matches

Open
#324,371 4 comments 2 reactions 1 assignee Claimed by @justschen View on GitHub
Dominant language
TypeScript
Stars
193k
Forks
42.4k
PR merge metrics
PR metrics pending

Description

- Copilot Chat Extension Version: 0.53.0
- VS Code Version: 1.125.0
- OS Version: Windows
- Feature (e.g. agent/edit/ask mode): agent mode (search tool rendering)
- Selected model (e.g. GPT 4.1, Claude 3.7 Sonnet): z-ai/glm-5.2 (3rd-party model via Copilot)
- Logs: Session debug log available on request (`main.jsonl` for sid `73fcdf07-0d2f-47a9-9f65-71e31c0b7318`). No `status:"error"` entry is emitted for the crash itself — the webview React exception is not captured by the JSONL log. Three `grep_search` `tool_call` entries are present (two with matches, one with 0 matches); all report `status:"ok"`.

## Related existing issues (checked for duplicates before filing)

I searched both `microsoft/vscode-copilot-release` and `microsoft/vscode` for duplicates. The closest existing report is **microsoft/vscode#260622** ("Cannot read properties of undefined (reading 'length')"), which is the **same renderer bug** but triggered by a *user-authored* MCP tool returning `text: undefined`. That issue was closed as `*out-of-scope` with the rationale that the MCP tool author should null-coalesce the `text` field themselves.

**This issue is not a duplicate of #260622**, because here the `undefined` is produced by **Copilot's own built-in search tool** (`grep_search` / `semantic_search` / `file_search`) on its **0-match branch**, not by a user-authored MCP tool. The user cannot null-coalesce the field on the tool side — the 0-match result shape is emitted by Copilot itself (see the JSON in "Tool result structure (0-match case)" below). So the `out-of-scope` reasoning from #260622 does not apply here; the fix has to land in the renderer.

Other nearby issues I checked, all of which are different bugs:
- `vscode-copilot-release#7662` — "Search with copilot report ERROR" (`reading 'folder'`,
old ext 0.26.3, closed `not-reproducible`)
- `vscode-copilot-release#9222` — "Unknown tool: semantic_search (at tsx element rr)"
(different error class: tool name unknown, not a 0-match render)
- `vscode-copilot-release#12731` — "Error on workspace search access" (duplicate, comments
contain `Unknown tool: "read_notebook_cell_output" (at tsx element ...)` and
`File not found (at tsx element ...)` — same `(at tsx element …)` renderer format but
different error messages; closed pointing back to the vscode repo)
- `vscode#259295` — "Workspace chunk search service not available (at tsx element fse > kee > KS > y0)"
(different: chunk-search service unavailable, not 0-match render)
- `vscode#258779` — "Error using 'search' tool in Copilot Chat request" (older, predates
the structured `r4e`/`p6e` renderer node shape)

## Summary

GitHub Copilot Chat throws a red-text error in the chat panel when a search tool (`grep_search` / `semantic_search` / `file_search`) returns **0 matches** (the "No matches found." branch). The error originates from the webview's React renderer, not from user code or the agent loop, and the agent loop itself reports `status:"ok"` — only the UI fails to render.

## Error message (verbatim)

```
Cannot read properties of undefined (reading 'length')
(at tsx element q4 > L5 > pGe > sEt > pl > > > Fkt > iEt > uEt > Jv)
```

The stack is minified (single/short identifiers like `q4`, `L5`, `pGe`, `Fkt`, `iEt`, `uEt`, `Jv`), and starts with `tsx element` — i.e. it is thrown during React/TSX rendering of a Copilot Chat webview component.

## Steps to Reproduce

1. Open a workspace that contains files which **would** match a search, but whose matches are excluded by `search.exclude` / `.gitignore` (e.g. `**/node_modules`, `**/*.code-search`). In my case the workspace is a Python repo containing `verl/` and `LlamaFactory/`.
2. In Copilot Chat (agent mode), ask the agent to do a workspace-wide search with an `includePattern`/query that ends up returning **0 matches**. The exact call that triggered the bug for me:
- Tool: `grep_search`
- `includePattern`: a directory under the user workspace that exists but yields 0 hits (I pointed it at the Copilot debug-logs directory, which is excluded by `search.exclude`)
- `query`: a regex present only in `search.exclude`'d locations
3. The tool returns `status:"ok"` with the standard "No matches found." body (see "Tool result structure (0-match case)" below).
4. The chat panel shows the response as **red error text**: `Cannot read properties of undefined (reading 'length')`.

## Root-cause analysis (from debug logs)

Inspecting the session debug log at
`…/User/workspaceStorage//GitHub.copilot-chat/debug-logs//main.jsonl`,
the same session contains three `grep_search` calls. Two return matches; one returns 0 matches.
The 0-match call has `status:"ok"` in the log — i.e. the tool layer considers the result valid —
but the **renderer** crashes on it.

### Tool result structure — matches case (renders fine)

The `result` JSON for a call with matches has `node.children` populated with
`ctorName:"r4e"` match-item nodes, each carrying a non-empty `references:[{anchor:{uri,range},...}]` array:

```json
{
"node": {
"type": 1, "ctor": 2, "ctorName": "ncn",
"children": [
{ "type": 1, "ctor": 2, "ctorName": "p6e", "children": [ { "type": 2, "priority": 20, "text": "50 matches (more results are available)" } ] },
{ "type": 1, "ctor": 2, "ctorName": "r4e",
"children": [ /* …match body, with a text node and an Mkt node… */ ],
"props": { "flexGrow": 1, "priority": 1 },
"references": [ { "anchor": { "uri": { "fsPath": "...", "scheme": "file", "path": "..." }, "range": [ { "line": 76, "character": 828 }, { "line": 76, "character": 831 } ] }, "options": { "isFromTool": true } } ],
"flags": 4
}
/* … more r4e items … */
],
"props": {},
"references": []
}
}
```

### Tool result structure — 0-match case (triggers the crash)

The `result` JSON for a 0-match call contains **only two plain-text leaf children** (`type:2`),
with **no `r4e` nodes, no `references`, no `children`, no `anchor`**:

```json
{
"node": {
"type": 1, "ctor": 2, "ctorName": "ncn",
"children": [
{ "type": 2, "priority": 9007199254740991, "text": "No matches found.", "lineBreakBefore": false },
{ "type": 2, "priority": 9007199254740991,
"text": "Your search pattern might be excluded completely by either the search.exclude settings or .*ignore files.\nIf you believe that it should have results, you can check into the .*ignore files and the exclude setting (here are some excluded patterns for reference:[**/node_modules,**/bower_components,**/*.code-search]).\nThen if you want to include files you can call the tool again by setting \"includeIgnoredFiles\" to true.",
"lineBreakBefore": false }
],
"props": {}, "references": []
}
}
```

The renderer, when iterating `node.children`, appears to assume each child is a structured
match node and reads a property like `child.references.length` (or `child.children.length`,
or a `range`/`anchor` array `.length`) without a null/empty guard. On the 0-match branch the
children are plain-text leaves, so the field is `undefined` and `.length` throws.

The minified stack `tsx element q4 > L5 > pGe > sEt > pl > > > Fkt > iEt > uEt > Jv`
is consistent with a React component (likely the search-results list / reference-chip /
range-interval renderer) not handling the 0-match leaf-node shape.

## Impact

- The error is shown as red text in the chat panel.
- The agent turn still completes from the model/tool-loop perspective (log shows `turn_end:ok`), so the model itself is fine — but the **rendered message is broken**, making the conversation hard to read and the previous tool result invisible to the user.
- Reliably reproducible whenever a search returns 0 matches; on a large workspace with `search.exclude` patterns this happens often.

## Expected behaviour

The 0-match branch should render the "No matches found." text as it does for other tools,
without throwing. Either:

1. Make the renderer guard `Array.isArray(child.references) ? child.references.length : 0`
(and equivalent for any other `.length` reads on optional fields), **or**
2. Make the search tool return a 0-match shape that matches the structured-match shape
(e.g. emit an empty `r4e`-style container with `references: []` instead of two bare
text leaves).

Option 1 is the more robust fix and should also cover the equivalent `semantic_search` /
`file_search` paths.

## Workarounds (for anyone hitting this)

- Re-issue the search with `includeIgnoredFiles: true` or a broader `includePattern` so the
call returns ≥1 match (avoids the 0-match render path).
- Start a New Chat session to drop the broken render state.
- `Developer: Restart Extension Host` to recover without losing the whole window.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.