search
Searches archived messages. FTS is the default mode and works without embeddings.
#Usage
discrawl search "panic: nil pointer"
discrawl search --mode fts "panic: nil pointer"
discrawl search --mode semantic "missing launch checklist"
discrawl search --mode hybrid "database timeout"
discrawl search --guild 123456789012345678 "payment failed"
discrawl search --dm "launch checklist"
discrawl search --channel billing --author steipete --limit 50 "invoice"
discrawl search --include-empty "GitHub"
discrawl --json search "websocket closed"
#Modes
fts(default) - SQLite FTS5 withunicode61; optional multilingual lexical fields use language-specific tokenizers and RRFsemantic- embeds the query, scores against locally stored vectors; errors out if embeddings are disabled or no compatible vectors existhybrid- runs both, deduplicates by message id, falls back to FTS when semantic is unavailable
Run discrawl lexical rebuild after enabling languages or replacing helpers/models. See search modes for installation and index lifecycle details.
#Flags
--mode <fts|semantic|hybrid>- search mode--guild <id>/--guilds <id,id>- restrict the guild scope--dm- shorthand for--guild @me--channel <id|name|#name>- resolve and restrict to one channel (exact id, exact name, or unique partial name)--author <name>- restrict to one author--limit <n>- cap result count--include-empty- include rows with no searchable content (attachment text/filenames, embeds, and replies still count as content)
#FTS behavior
User query terms are parameterized and quoted before MATCH, so tokens like AND, OR, NOT, NEAR, and * are searched as input terms instead of FTS operators. Punctuation still follows FTS5 tokenization rules.
When [search.lexical].languages is non-empty, Discrawl searches the default field plus every configured language field, ranks each field independently, and merges the lists with reciprocal rank fusion. See Search modes for setup and dependency details.
Ambiguous channel names fail with candidate guild/channel ids instead of silently searching multiple channels. Use discrawl channels resolve <name> --json, then keep the numeric id for repeatable workflows.
#Semantic prerequisites
[search.embeddings]configured in the Discrawl config file- local
message_embeddingsrows for the configured provider, model, and input version - input version is currently
message_normalized_v1
Run discrawl sync --with-embeddings to enqueue, then discrawl embed to generate vectors.
#Empty results
A search that matches nothing can print a note to stderr naming the likely cause and a suggested next step. The causes covered are: the channel has no messages in the local mirror (with a follow-up for a forum id, whose posts live in separate thread channels); every message in the channel is empty or attachment-only and so dropped by the default content filter; the query has several terms and one of them matches on its own, because every term is required; a --mode semantic or --mode hybrid run over a scope whose messages carry no embeddings for the configured provider and model; and a --channel that exists but sits in a guild outside the --guild/--guilds scope, which names the guild to use instead.
The embedding-coverage note names the command that applies to the scope it found. With pending non-empty embedding jobs in scope, discrawl embed processes the archive-wide queue up to --limit (default 1000) per run; other jobs can come first, so repeated runs may be needed. With none, discrawl embed has nothing to drain, so the note names discrawl embed --rebuild, which is the supported way to enqueue the missing jobs and requeues every non-deleted message outside DMs archive-wide rather than only the queried scope. Direct messages are never queued for embedding at all, so a DM-scoped note recommends --mode fts and names no embed command.
These notes are diagnostics, not output:
- they go to stderr, never stdout
--jsonsuppresses them, so machine-readable output is unaffected- stdout bytes and the exit code are unchanged either way, so stdout-only pipelines retain their existing output; consumers merging stderr into stdout will see the new notes
With --author, only lexical term hints are emitted because they preserve the query’s author matching.
A run that resolved --channel to a name fragment rather than a concrete id gets no note, because the counts behind one cannot reproduce that row set.
#See also
- Search modes
- Embeddings
messages- exact slices, not search