Steps to reproduce
Run the following four eval commands from any shell with obsidian.exe on PATH and a vault open. Tested on Obsidian 1.12.7 (installer 1.12.4) on Windows 11.
TEST A — sync-only async + console.log: output appears as expected
obsidian eval --allow-focus-steal ‘code=(async () => {
console.log(JSON.stringify({a: 1}));
})()’
Output: {“a”:1}
TEST B — await Promise.resolve() + console.log: output appears
obsidian eval --allow-focus-steal ‘code=(async () => {
await Promise.resolve();
console.log(JSON.stringify({b: 2}));
})()’
Output: {“b”:2}
TEST D — read-only async + console.log: output appears
obsidian eval --allow-focus-steal ‘code=(async () => {
const f = app.vault.getAbstractFileByPath(“path/to/some-existing-file.md”);
const c = await app.vault.read(f);
console.log(JSON.stringify({len: c.length}));
})()’
Output: {“len”:67} (or whatever the file length is)
TEST E — vault-mutating await + console.log before AND after: NO OUTPUT
obsidian eval --allow-focus-steal ‘code=(async () => {
console.log(“before-await”);
const f = app.vault.getAbstractFileByPath(“path/to/some-existing-file.md”);
await app.fileManager.trashFile(f);
console.log(“after-trash”);
})()’
Output: (empty)
Did you follow the troubleshooting guide? [Y]
Expected result
In TEST E, both "before-await" and "after-trash" should appear on stdout, in that order, since console.log is synchronous and the calls bracket the awaited operation.
Actual result
In TEST E, stdout is empty. The => return-value line that the eval CLI normally prints is also missing. The vault mutation itself (trashFile) completes successfully — the file is moved to trash and the operation is durable — but neither the pre-await console.log nor the post-await console.log reach stdout.
The pattern is consistent: as long as the async block awaits a vault-mutating Obsidian API (app.fileManager.renameFile, app.fileManager.trashFile, app.vault.modify, etc.), ALL console.log output (including calls that execute synchronously before the await) and the eval return value disappear from stdout. Read-only awaits (app.vault.read, app.metadataCache.*) do NOT trigger the loss; only vault-mutating awaits do.
Why this is a problem
Tools that wrap obsidian eval to perform atomic file/folder operations (e.g. fileManager.renameFile for a folder rename that needs to cascade wikilinks) cannot use console.log or the return value as a success signal. Callers must work around this by re-checking state through the Local REST API plugin or another channel after the eval, which adds latency and complexity that should not be required.
Workarounds I’m using
-
Ignore eval stdout entirely; verify the mutation landed via a separate REST GET after the eval.
-
Wrap the rename in a higher-level command (
fs:renamein my routing wrapper) that does pre-check, eval, REST verify, and only reports success if the verify lands.
These workarounds are sufficient for my own tooling, but the root cause appears to sit in the CLI plugin’s stdout-handling around Promise resolution for vault-mutating APIs. A fix at the source would let consumers trust eval output again.
Related observation: non-deterministic --allow-focus-steal
A secondary observation, possibly worth a separate report or just a documentation clarification: three identical eval invocations within the same minute produced 0× actual focus-steal on the first call and 2× on the following. The flag appears to permit focus-steal but not require it, and Obsidian’s internal scheduling decides whether the window actually grabs focus. This is fine if intentional, but documenting it explicitly would help — currently consumers may assume the flag deterministically grabs focus and gate logic on that assumption.
Environment
-
Obsidian: 1.12.7 (installer 1.12.4)
-
OS: Windows 11 Pro 22H2
-
Local REST API plugin: 3.6.x (used for verification but unrelated to the bug)
-
Reproduced consistently across multiple sessions on 2026-04-26 and 2026-04-27.
