tobi/qmd · error · Error
${location} (${search.type}): queries must be single-line. R
Error message
${location} (${search.type}): queries must be single-line. Remove newline characters. What it means
The multi-search validator rejects any query containing \r or \n because each search's query must fit on a single line (they are interpolated into single-line SQL/FTS statements). The error is prefixed with the search's line number or 'Structured search'.
Source
Thrown at src/store.ts:5883
options?: StructuredSearchOptions
): Promise<HybridQueryResult[]> {
const limit = options?.limit ?? 10;
const minScore = options?.minScore ?? 0;
const candidateLimit = options?.candidateLimit ?? RERANK_CANDIDATE_LIMIT;
const explain = options?.explain ?? false;
const intent = options?.intent;
const skipRerank = options?.skipRerank ?? false;
const hooks = options?.hooks;
const collections = options?.collections;
if (searches.length === 0) return [];
// Validate queries before executing
for (const search of searches) {
const location = search.line ? `Line ${search.line}` : 'Structured search';
if (/[\r\n]/.test(search.query)) {
throw new Error(`${location} (${search.type}): queries must be single-line. Remove newline characters.`);
}
if (search.type === 'lex') {
const error = validateLexQuery(search.query);
if (error) {
throw new Error(`${location} (lex): ${error}`);
}
} else if (search.type === 'vec' || search.type === 'hyde') {
const error = validateSemanticQuery(search.query);
if (error) {
throw new Error(`${location} (${search.type}): ${error}`);
}
}
}
const rankedLists: RankedResult[][] = [];
const rankedListMeta: RankedListMeta[] = [];
const docidMap = new Map<string, string>(); // filepath -> docid
const hasVectors = !!store.db.prepare(View on GitHub (pinned to dbfd0b4736)
Solutions
- Strip newlines before passing: query.replace(/[\r\n]+/g, ' ').trim()
- Split multi-line input into separate search entries if each line is a distinct query
- Validate input client-side before calling searchMulti
Example fix
// before
searchMulti([{ type: 'lex', query: multiLineString }]);
// after
searchMulti([{ type: 'lex', query: multiLineString.replace(/[\r\n]+/g, ' ').trim() }]); Defensive patterns
Strategy: validation
Validate before calling
const clean = q.replace(/[\r\n]+/g, ' ').trim(); if (!/[\r\n]/.test(clean)) searchMulti([{ type, query: clean }]); Type guard
const isSingleLine = (q: string) => !/[\r\n]/.test(q);
Prevention
- Normalize user input to single lines at the boundary
- Never paste multi-line template literals as queries
- Add form-level validation for textareas
When it happens
Trigger: Passing a searches array where any entry's query string contains a newline — e.g. a template literal spanning lines, or a user query pasted with a trailing newline into searchMulti().
Common situations: Multi-line JS template strings used for queries; user input from textareas passed directly; log/env strings with embedded \n.
Related errors
- search() requires either 'query' or 'queries'
- ${location} (lex): ${error}
- ${location} (${search.type}): ${error}
- Line ${line.number} starts with expand:, but query documents
- expand: query must include text.
AI-assisted analysis of tobi/qmd@dbfd0b4736 (2026-08-28).
Data as JSON: /api/errors/2c344f4d7b2e43e9.
Report an issue: GitHub.