CLI `search` subcommand fails silently (exit 127) when query contains operators like tag:, path:, file:

Steps to reproduce

On Windows, run the built-in CLI with a search query that contains any of Obsidian’s search operators (tag:, path:, file:, line:):

OBS="/c/Program Files/Obsidian/Obsidian.exe"
for q in 'MeToo' 'tag:#MeToo' 'tag:inbox' 'path:Facebook' 'file:Libya' 'line:inbox'; do
  "$OBS" search query="$q" format=json 2>/tmp/err 1>/tmp/out
  echo "query=\"$q\"  exit=$?  stdout=$(wc -c < /tmp/out)B  stderr=$(wc -c < /tmp/err)B"
done

Plain-text queries work. Any query containing an operator fails.

As a sanity check, the in-app search engine itself handles these queries correctly - invoking the search plugin directly viaeval works:

"$OBS" eval code='app.internalPlugins.getPluginById("global-search").instance.openGlobalSearch("tag:#MeToo")'

So the engine is capable; the search CLI handler is the layer that drops the query.

Did you follow the troubleshooting guide? Y

Reproduced in the Sandbox vault as well. Restricted mode on, all community plugins disabled -same behavior. Reproduced in both Git Bash (MSYS2) and PowerShell 7.

Expected result

Either:

  1. The CLI returns results matching what in-app search returns for the same query (operators supported), or
  2. The CLI prints a clear error on stderr explaining that operators are not supported, with a documented non-zero exit code.

Actual result

Every query containing an operator exits with code 127, zero bytes on stdout, and zero bytes on stderr:

query="MeToo"          exit=0    stdout=209B   stderr=0B
query="tag:#MeToo"     exit=127  stdout=0B     stderr=0B
query="tag:inbox"      exit=127  stdout=0B     stderr=0B
query="path:Facebook"  exit=127  stdout=0B     stderr=0B
query="file:Libya"     exit=127  stdout=0B     stderr=0B
query="line:inbox"     exit=127  stdout=0B     stderr=0B

Same behavior with default text format and with format=json. Silent exit 127 with no stderr makes it impossible for a caller to distinguish “operator unsupported” from “query ran fine, zero matches” from “CLI crashed”.

Environment

SYSTEM INFO:
Obsidian version: 1.12.7
Installer version: 1.12.4
Operating system: Windows 11 Pro 10.0.26200
Login status: not logged in
Language: en-GB
Insider build toggle: off
Live preview: on
Base theme: adapt to system
Community theme: none
Snippets enabled: 2
Restricted mode: off
Plugins installed: 25
Plugins enabled: 21
1: Update time on edit v2.4.0
2: TimeStamper v1.3.0
3: Copilot v3.2.1
4: Commander v0.5.4
5: Callout Manager v1.1.0
6: Advanced Tables v0.22.1
7: Style Settings v1.0.9
8: Highlightr v1.2.2
9: BRAT v1.3.2
10: Auto Keyword Linker v3.3.0
11: Settings Search v1.3.10
12: Omnisearch v1.27.3
13: Local REST API v3.4.3
14: Dataview v0.5.68
15: Automatic Table Of Contents v1.7.3
16: Media Notes v1.3.0
17: Excalidraw v2.18.2
18: Editing Toolbar v3.2.1
19: Templater v2.16.4
20: Claudian v2.0.2
21: TOC compatible with Publish v3.1.0
RECOMMENDATIONS:
Custom theme and snippets: for cosmetic issues, please first try updating your theme and disabling your snippets. If still not fixed, please try to make the issue happen in the Sandbox Vault or disable community theme and snippets.
Community plugins: for bugs, please first try updating all your plugins to latest. If still not fixed, please try to make the issue happen in the Sandbox Vault or disable community plugins.

Obsidian 1.12.7 (installer 1.12.4), Windows 11 Pro 10.0.26200.

Additional information

External tooling that wraps Obsidian.exe search for tag-based or path-scoped queries silently returns “no matches” without any signal of failure. In our case this caused a multi-hour debugging session pointing at the wrong suspect (MSYS2 argv encoding, emoji handling) before we isolated the actual trigger: “query contains a : operator”. A stderr message alone would have saved
hours.

Suggested fix priority:

  1. Emit a clear stderr message and a documented exit code when the CLI cannot handle a query.
  2. Document in Obsidian.exe help search which operators are supported.
  3. Ideally, route CLI search through the same query parser as in-app search so operators work uniformly.

the correct executable for obsidian cli on windows is obsidian or obsidian.com, not obsidian.exe

Re: .com vs .exe — ran parity test on same query strings, same session:

Query Obsidian.com Obsidian.exe
search query=‘path:“Facebook Posts”’ exit 0, 7 results exit 0, 7 results
search query=“tag:#X exit 0, 18 results exit 0, 18 results

Output is bit-identical. .com is a console shim over the same binary. It changes stdio attachment, not argument parsing. Exit 127 originates from the Obsidian process itself, not from the shell failing to locate a binary (Git Bash would say command not found). So the binary is being found and executed regardless; the failure is inside the search subcommand’s operator handling.

Important correction to my original report: the failure is intermittent, not deterministic. My original post showed 5/5 exit-127 on operator queries, and that was real at the time, reproduced across Git Bash and PowerShell. But a scheduled probe I’ve been running every 10 minutes for hours over the past 48 hours has logged 43/43 exit-0 runs of the same operator query. In some Obsidian sessions, operators work; in others, they fail silently with exit 127. Same binary, same query string, same vault.

What I don’t yet know: what state inside Obsidian flips the switch. Cold-start reproduction will miss it. Long-running sessions seem more likely to trigger it, but I don’t have a confirmed trigger yet. I’ll post the probe’s state snapshot when it catches the transition.

The core claim stands: when the bug fires, CLI search returns exit 127 with zero stdout and zero stderr for any operator query, while the same query routed through openGlobalSearch via eval succeeds. The silent-exit signature is the real problem regardless of how often it fires, a caller cannot distinguish “bug” from “no matches”.

Update after a full day of follow-up testing — two findings.

1. Exit 127 still not reliably reproducible.

Ran 12 controlled scenarios against the same vault + Obsidian instance where the original 5/5 exit-127 failure was captured. Zero exit 127 in any of them. Scenarios included: cold Obsidian restart, warm process, community plugins on/off (Restricted Mode), long idle (12+ min), active UI session, Obsidian’s built-in “Rebuild vault cache”, and live incremental re-index via touchon
file mtimes. Original 5/5 is still the only capture. Consistent with “intermittent, session-dependent” as previously noted.

2. Discovered a separate, deterministic silent-failure mode.

After an Obsidian cold start, Obsidian.exe search returns exit 0 with empty JSON array stdout (2 bytes) for ALL queries, including queries that trivially have matches in the vault, like search query="path:Facebook" in a vault with a Facebook Posts/ folder containing many files. Plain text queries, tag queries, path queries. All return []. This persists indefinitely
until something initializes internal search-plugin state.

The warmup call I found:

  "$OBS" eval code="app.internalPlugins.getPluginById('global-search').instance.openGlobalSearch('anything')"

After that single eval call, subsequent Obsidian.exe search query=... calls return real results (no search view needs to persist in the UI).

Caller-side impact: exit 0 + empty-stdout for path:X is indistinguishable from “query ran fine, no matches”. Any tooling wrapping Obsidian.exe search without first invoking openGlobalSearch via eval silently returns zero results after every Obsidian restart, no signal anything is wrong. This is likely why my scheduled probe had logged 43/43 “exit 0” between incidents. Most of those were silent cold-state, not real passes. Probe has been updated to distinguish cold vs warm responses.

Suggested fix: the search subcommand should ensure internal search-plugin state is initialized before returning, transparently, without requiring callers to first invoke eval.

3. Strong negative evidence against “bug fires during re-indexing” hypothesis.

I instrumented a probe to capture app.metadataCache.inProgressTaskCount alongside CLI search results, to test whether active indexing correlates with exit-127.

Captured two full re-index cycles with concurrent CLI probing:

Test A — Settings → Files and links → Rebuild vault cache (full metadata wipe + rebuild):

  t      inProgressTaskCount    metadataCacheSize
  +33s                   558                    4      <- rebuild started, cache wiped
  +37s                   512                   49
  +40s                   468                   93
  +47s                   403                  155
  +53s                   363                  195
  +57s                   340                  218      <- CLI starts returning data (116B)
  +60s                   231                  327
  +64s                    87                  470
  +67s                     0                  556      <- complete

~36 operator-CLI calls fired during the active rebuild window. Zero exit 127.

Test B — live incremental re-index (touched 30 file mtimes in a running Obsidian, file watcher triggered rescan):

  t      inProgressTaskCount    metadataCacheSize
  +0s                     23                  558      <- index started
  +4s                     22                  562
  +8s                      0                  584      <- complete

9 operator-CLI calls fired during inProgressTaskCount > 0. Zero exit 127.

Both tests: CLI returned empty (cold-state) during active indexing but never hit the exit-127 signature. Re-indexing, whether full wipe or incremental, is not the trigger.

State-snapshot for any future captures

If anyone else wants to capture the exit-127 state alongside indexing context, the snapshot I use is:

  Obsidian.exe eval code="const m=app.metadataCache; JSON.stringify({pid: process.pid, uptime: Math.round(process.uptime()),

initialized: m.initialized, tagCount: Object.keys(m.getTags?.() || {}).length, inProgressTaskCount: m.inProgressTaskCount, mdFiles: app.vault.getMarkdownFiles().length, metadataCacheSize: Object.keys(m.metadataCache || {}).length})"

Will follow up if my probe catches the exit-127 signature again with enriched state.

I am not sure if this is your style of writing or if you are being assisted by an LLM. It’s very confusing.

I will look into what you are reporting.

From this point on, you must:

  1. Download and reinstall Obsidian.
  2. Enable restricted mode and restart Obsidian.
  3. Perform the test from Window console or Window powershell (no MSYS, or WSL, we don’t support them)
  4. Perform the test directly and manually (without agent intermidiary)
  5. Call Obsidian CLI from the terminal with obsidian or obsidian.com (no obsidian.exe)

Hi WhiteNoise. Thanks for the response, and apologies for the density and perhaps lack of clarity of my reports. I use an LLM (Claude Code) to help structure technical findings, which is great for thoroughness but might bury the actual question. I’ll try to keep this short.

On the clean-room reproduction you requested: I attempted this but ran into a fundamental problem. My follow-up testing established that the failure is intermittent, not deterministic. The original 5/5 exit-127 result held briefly, but a scheduled probe running every 10 minutes for 48+ hours then logged 43/43 successes against the same operator query. I’ve also run controlled scenarios (cold/warm Obsidian process, plugins ON/OFF in Restricted Mode, idle vs. active session, full vault cache rebuild, incremental re-index). Zero exit 127 events across all of them.

A clean-room PowerShell/CMD cold-start test will almost certainly show 0 failures and prove nothing. The trigger appears to be some long-running session state I cannot manipulate directly (IndexedDB state, plugin init order, sync-event sequence; speculation, not evidence).

One related finding worth recording, in case it’s useful: on cold start, obsidian search returns silent exit 0 with empty stdout for all queries (including trivial path: matches that should match dozens of files) until openGlobalSearch is invoked once via eval. This part is 100% reproducible on cold start and may or may not be related to the exit-127 issue.

My practical workaround: I’ve routed all search through the local REST API plugin (openGlobalSearch via eval), which bypasses the CLI search codepath entirely. That eliminates the bug for my use case.

Given I can’t deliver a deterministic repro, feel free to mark this as needs-more-info or close it. I don’t want to take up the team’s time on something I can’t pin down. The probe script will keep running; if it ever catches the exit-127 transition with a usable state snapshot, I’ll post it here.