{"record":{"id":"9dd60d230cd5f976","repo":"windmill-labs/windmill","slug":"search-app-requires-a-non-empty-query","errorCode":null,"errorMessage":"search_app requires a non-empty query.","messagePattern":"search_app requires a non-empty query\\.","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"frontend/src/lib/components/copilot/chat/global/core.ts","lineNumber":5697,"sourceCode":"\t\treturn false\n\t}\n}\n\ntype AppSearchMatch = { filePath: string; line: number; text: string }\n\nasync function searchApp(\n\targs: {\n\t\tpath: string\n\t\tquery: string\n\t\tfile_glob?: string\n\t\tmax_matches?: number\n\t},\n\tctx: WriteDraftCtx\n): Promise<string> {\n\tconst { workspace, toolId, toolCallbacks } = ctx\n\tconst query = args.query\n\tif (query.length === 0) {\n\t\tthrow new Error('search_app requires a non-empty query.')\n\t}\n\ttoolCallbacks.setToolStatus(toolId, {\n\t\tcontent: `Searching app \"${args.path}\" for \"${query}\"...`\n\t})\n\n\tconst value = await loadAppValueForRead(args.path, workspace)\n\tconst maxMatches = Math.min(\n\t\targs.max_matches ?? SEARCH_APP_DEFAULT_MAX_MATCHES,\n\t\tSEARCH_APP_MAX_MATCHES_CEILING\n\t)\n\tconst contextLines = SEARCH_APP_CONTEXT_LINES\n\tconst needle = query.toLowerCase()\n\n\tlet files = collectSearchableAppFiles(value).sort((a, b) => a.filePath.localeCompare(b.filePath))\n\tif (args.file_glob) {\n\t\tfiles = files.filter((f) => appFileMatchesGlob(f.filePath, args.file_glob as string))\n\t}\n","sourceCodeStart":5679,"sourceCodeEnd":5715,"githubUrl":"https://github.com/windmill-labs/windmill/blob/e474e8803ce2ff5c2df09a58dab51d45f5c922ca/frontend/src/lib/components/copilot/chat/global/core.ts#L5679-L5715","documentation":"The search_app copilot tool in the AI chat's global mode refuses to run when the model supplies an empty `query` argument. The tool searches the contents of an app file, and an empty query would be meaningless. This is an argument-validation guard thrown before any work starts.","triggerScenarios":"The AI model calls the search_app tool with `args.query` set to \"\" (empty string), typically when it omits the query or fills it with an empty value while attempting to search an app at `args.path`.","commonSituations":"LLM tool-call hallucination or truncation drops the query field; a caller-built tool invocation constructs args programmatically and the query variable was empty; prompt instructions didn't make clear the query is mandatory.","solutions":["Re-issue the tool call with a non-empty query string describing what to search for in the app","If building the call programmatically, check `query.trim().length > 0` before invoking","Improve the model's system/tool prompt so it always supplies a query"],"exampleFix":"// before\nsearch_app({ path: 'f/app_main', query: '' })\n// after\nsearch_app({ path: 'f/app_main', query: 'handleFormSubmit' })","handlingStrategy":"validation","validationCode":"if (typeof args.query !== 'string' || args.query.trim().length === 0) {\n  throw new Error('search_app requires a non-empty query.')\n}","typeGuard":"function hasQuery(args: { query?: string }): args is { query: string } {\n  return typeof args.query === 'string' && args.query.length > 0\n}","tryCatchPattern":"try {\n  await searchApp(args)\n} catch (e) {\n  if (e instanceof Error && e.message.includes('non-empty query')) {\n    // retry with a corrected, non-empty query\n  }\n}","preventionTips":["Always derive the query from content the model actually produced, never from an unbound variable","Validate tool arguments against the tool's JSON schema before dispatch","Include 'query is required' in the tool description/prompt examples"],"tags":["ai-agent","tool-arguments","validation"],"backgroundTag":"empty-required-argument","analyzedSha":"e474e8803ce2ff5c2df09a58dab51d45f5c922ca","analyzedAt":"2026-09-03T12:38:19.024Z","contentChangedAt":"2026-09-03T12:38:19.024Z","schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}