Troubleshooting
What to do when sync pauses, a note looks unsaved, search finds nothing, an installer is blocked, or you need to restore something you deleted.
Most Jotura problems fall into a handful of categories, and almost all of them have a specific message attached. This page is organized by what you are looking at. Where a fix needs the command line, the exact command is given.
Before anything else, keep in mind that nothing in Jotura blocks local work: reading, writing, and keyword search never wait on sync, on a key, or on the network. An unresponsive editor is therefore a different problem from any of the sync states below.
Start with the built-in checks
The jotura doctor command runs a sequence of checks over the vault, sync state, shares, documents, semantic search, and the bundled document converter, then prints the results.
jotura doctor
Add --json when a script needs to read the result:
jotura doctor --json
Doctor emits an array of { name, status, detail } objects, where status is one of pass, warn, fail, or info. The checks are emitted in this order:
| Check | What it tells you |
|---|---|
cli-on-path |
The current executable path, and whether its directory is on $PATH. |
cli-version |
The version of the CLI binary you just ran. |
vault-detected |
The resolved vault path, or advice to set $JOTURA_VAULT. |
vault-readable |
Whether the vault directory lists cleanly. |
sync-enabled |
Whether the vault has sync configured, plus active or paused and the pending op count. |
sync-key-cached |
Only emitted when sync is enabled. Advises jotura sync login when the key is missing. |
shares-registry |
Whether the share registry parses, and how many entries it holds. |
share-mount |
One per share. Whether the registry’s mount folder exists on disk. |
share-sync-db |
One per share. Sync database existence and pending op count. |
share-key-cached |
One per share. Whether that share’s key is cached on this device. |
documents |
Converted, failed, pending, and total document counts. |
documents-failed |
Up to 10 failing document paths, emitted as one check each. |
documents-failed-note |
Points you at regenerate-md-store --stale-only. |
search-cache |
The path and size of the CLI’s cached keyword index, or absent. |
semantic-index |
Missing, disabled, unopenable, or enabled with chunk, pending, and skipped counts and the provider that built it. |
semantic-key |
OpenAI indexes only. Whether an API key is available and whether it came from the environment or the saved key file. Never the key itself. |
semantic-model |
Local indexes only. The model directory path, or that the files are missing. |
semantic-runtime |
Local indexes only. Whether the embedding runtime loads, and which backend it picked. |
desktop-app-version |
The appVersion recorded in the desktop app’s settings.json. |
markitdown-sidecar |
The document converter’s version, or that it was not found next to the CLI. |
markitdown-sandbox |
Whether a usable operating system sandbox is available for document conversion. |
diagnostics-log |
The path to the local diagnostics log. |
diagnostics-recent |
Up to five of the most recent log lines, emitted as one check each. |
The array is longer than that list of names, because share-* repeats per share, documents-failed repeats per failing document, and diagnostics-recent contributes up to five entries when the log exists.
Scripts that call doctor need to know that it always exits 0, even when a check has status fail, so its exit code cannot serve as a health gate and the JSON has to be parsed instead. It is also deliberately vault-tolerant: it resolves the vault itself and reports a missing vault as a failed check rather than exiting with an error.
Five checks can report fail: vault-detected, vault-readable, sync-enabled, share-mount, and markitdown-sandbox. No semantic check goes beyond warn, because keyword search keeps working whatever the embedding index is doing.
The second diagnostic lives in the app. Open Settings, expand Advanced, then Diagnostics. Copy diagnostics puts a support bundle on your clipboard, and Log file shows the path to the local log. Nothing is sent anywhere; you paste the bundle into a bug report yourself.
My note did not save
Jotura has no Cmd+S or Ctrl+S. Saving is automatic and there is no manual save command anywhere in the app, so pressing the chord does nothing and shows nothing.
The editor writes to disk one second after your last change. It also flushes immediately when the editor loses focus and when the window becomes hidden, so switching apps or closing the window saves your work without waiting out the timer. Every save goes through a serial queue, so an autosave timer and a blur flush can never conflict with each other.
The signs that a save happened are quiet. A tab with unsaved changes shows a single • after its title, and the app announces Saved to screen readers when the write lands. Read-only tabs never become dirty and never autosave.
The one way to lose an edit is to force-quit the app within a second of typing, before the timer fires and without the window losing focus first. Clicking outside the editor, or switching to another application, closes that window.
Sync says it is paused
Sync has several distinct paused states and they need different fixes. The footer pill and the banner above the tab bar both name the state, and Settings then Account & sync shows the full status row.
| What you see | What it means | What to do |
|---|---|---|
Sync paused. Enter your sync password. |
The vault is sync-enabled but this device has no cached key. | Click Unlock on the banner, click the Sync paused pill in the footer, or open Settings and use the Unlock sync… button in the Sync password needed row. |
Sync paused. You're signed out. |
There is no account session on this device. | Click Sign in on the banner or the footer pill, then complete the email code flow. |
Paused. Server copy preserved. Resume to push pending edits. |
Sync was paused deliberately. | Open Settings and click Resume sync. |
Subscription needed |
The account has no active Sync subscription. | Sync is the one paid part of Jotura. See pricing. |
Offline. Will retry when online. |
The device reports no network. | Nothing. Sync resumes when the connection returns. |
Error: <message> |
A push or pull failed. | Read the message, then work through the sync diagnostics rows under Advanced, then Diagnostics in Settings. |
The banner text itself is not a click target. Each banner carries two buttons, the action (Unlock or Sign in) and Dismiss, and dismissal is remembered per vault. In the footer pill, only the Sync paused and Sign in states are buttons, plus Sync error when the local sync records are damaged, which opens the reset; the rest are plain status text.
The missing-key case is the common one, and nothing is broken when you hit it: your notes stay fully readable and editable while sync is paused, and edits queue locally and push once the key is back.
You can supply the key from either surface. In the app, enter your sync password in the unlock modal, or use the 12-word recovery phrase if you have lost the password. From the terminal:
jotura sync login
That prompts for the password with no echo and caches the key. To unlock with the recovery phrase instead:
jotura sync login --recovery-phrase
To feed the secret from a script rather than a prompt:
jotura sync login --passphrase-stdin
A wrong password or an invalid recovery phrase both exit 9 (InvalidArgs) rather than a dedicated authentication code.
The desktop app and the CLI share one per-vault key cache under <config>/jotura/sessions/ (%LOCALAPPDATA%\jotura\sessions\ on Windows), so unlocking in either surface resumes the other. On Unix the cache files are mode 0600 inside a 0700 directory. On Windows they are encrypted with your Windows account (DPAPI). A session that cannot use DPAPI, such as a key-based SSH login, cannot open them and reports the key as not cached unless an older plaintext copy is still in %APPDATA%\jotura\sessions\; set JOTURA_SECRET_STORE=plain for both the SSH session and the desktop to share plaintext files in %APPDATA%\jotura instead.
Check the state at any time:
jotura sync status
That reports enabled, keyCached, paused, pendingOps, and a per-share array. On a vault without sync it reports enabled: false instead of erroring. By contrast, jotura sync login, jotura sync logout, and jotura sync drop-path exit 12 (SyncNotEnabled) on a vault that has no sync configured.
Enabling sync, setting or changing the sync password, and restoring a vault from the cloud all happen in the apps. The CLI only manages the local key cache and reports state.
If you deliberately cleared the key with Forget key on this device in Settings under Security & recovery, the paused state is expected. That action clears the in-memory keys and deletes the session cache, so sync moves to the needs-key state while local editing carries on untouched.
More on how sync behaves: Sync and sharing and Sync and shares from the CLI.
Changes from another device are not appearing
Sync is a poll rather than a live push: every 5 seconds while the window has focus, and every 30 seconds (60 on battery) in the background, so a short delay is normal. When a change is stuck, the app has a dedicated repair.
Open Settings, expand Advanced, then Diagnostics, and find Re-pull from server among the sync diagnostics rows (they appear only on a vault with sync enabled). Its Force re-sync button resets the local sync cursor to zero and re-fetches the entire manifest. It confirms first with Re-fetch the entire manifest from the server? Local pending edits are kept.
Diverged text is auto-merged into the file, and anything that cannot be merged keeps your local copy. Sync writes a separate conflict copy only when a merge is too large to attempt, more than 10,000 lines on a side.
The diagnostics rows above that button are the useful reading while you are here: Account, Device ID, Vault ID, Pending ops, Server version, Last push, Last pull, Last push error, and Last pull error. A non-empty error row is the fastest answer to why nothing is moving.
One repair lives only in the CLI. If a path is tracked in sync under a stale or case-aliased string, for example a daily-notes folder that once synced as daily and now lives at Daily, drop the stale string from tracking:
jotura sync drop-path "daily/2026-08-29.md"
It matches the synced-paths table by exact string without consulting the filesystem, and it never touches local files. It exits 3 (NotFound) when no synced row matches, and 9 (InvalidArgs) for an empty path or anything under .jotura/. The deletion propagates to your other devices on the app’s next sync.
Never fix a case-aliasing problem by deleting the file instead. A file delete propagates as a real deletion to every device, and you lose the note.
A share is misbehaving
Shared folders have their own failure modes, and one of them can destroy other people’s copies if you ignore it.
A share-mount check reporting fail means the share registry points at a folder that no longer exists on disk. Left alone, that stale entry can propagate a share-wide deletion the next time the desktop app launches, which affects everyone in the share. Fix it in the desktop app by re-mounting the share, or by leaving the share outright. The same condition shows up in the CLI as MISSING in the MOUNTED column:
jotura shares list
A shares-registry check reporting warn means the registry file does not parse. Reads treat a broken registry as empty, so your shares vanish from listings, while writes are refused until it is repaired. If shares disappear from the sidebar at the same moment that edits start failing, both symptoms come from this one problem.
Status dots in the Shared pane carry their explanation in the tooltip. The exact strings are Not syncing yet, Up to date, Syncing…, Needs your sync key, Your role changed. Edits here can't be saved, Subscription needed to keep this share syncing, Sync error: {message}, Offline, will retry, Paused, Not syncing, and Waiting for another member to re-seal this share to your key.
Writes into a share you can only view are refused rather than queued. From the CLI that is exit 15 (ReadOnlyShare), and the error JSON carries path, shareId, and mountRel. There is deliberately no override flag, because a forced viewer write would never sync and would be overwritten by the next remote change. Ask the owner for editor access.
On mobile, a 403 from the server after a role change means a role block. The share is still yours, but you have lost write access to it.
Share management itself, meaning inviting, promoting, and removing members, is desktop-only. Leaving a share also works on the phone. A machine with only the CLI installed also cannot receive a new share, because mounting happens when the desktop or mobile app runs discovery. Once a share is mounted, the CLI is fully aware of it.
Search finds nothing
Most reports of broken search trace back to one of four surfaces doing what it was designed to do, which is not always what the reader expected.
| Surface | How you open it | What it searches |
|---|---|---|
| Sidebar search pane | The Search icon in the sidebar, the Dashboard’s Search vault tile, or the Open Search Panel command | Keyword, Semantic, or Smart. Keyword mode searches titles and content, with separate Titles (N) and Content (N) result groups |
| Search page | Cmd/Ctrl+Shift+F | Keyword, Semantic, or Smart, whichever mode is selected |
| Quick open | Cmd/Ctrl+P | The file name and frontmatter aliases only. Not the path, not the note content |
| Source mode | Cmd/Ctrl+Shift+E inside a note | Nothing. There is no in-pane find |
Three consequences are worth spelling out.
Quick open matching only names and aliases is the single most common surprise. Searching it for a phrase you know is inside a note returns nothing, and there is no > prefix for commands and no #tag filter to fall back on. Use the search page for content.
The Dashboard’s Search vault tile and the Open Search Panel command both open the sidebar pane. Cmd/Ctrl+Shift+F opens the separate search page. Both offer Keyword, Semantic, and Smart modes once semantic search is on.
Source mode installs only CodeMirror’s default and history keymaps, so Ctrl+F does nothing there. Toggle back to the editor and use the search page instead.
If body results look incomplete right after opening a vault, check the sidebar for the strip reading Indexing… {done}/{total}. Content results are partial until that clears. Full detail on all of this is on the Search page.
From the terminal, jotura search keeps its own keyword index in .jotura/cli-search/ and re-indexes only the notes that changed since the last run. In a vault without a .jotura/ folder, such as an Obsidian vault Jotura has never written to, and for any search scoped with --in, it builds a throwaway in-memory index on every invocation instead, so do not put those in a loop.
jotura search "quarterly review"
Semantic and Smart search are greyed out
The search page offers three modes, Keyword, Semantic, and Smart. Semantic and Smart stay disabled until semantic search is enabled for the open vault, with the tooltip Enable semantic search to use this mode. Because the stored default mode is smart, the page silently falls back to keyword rather than failing when the model is absent, which is why the greyed-out buttons are often the only clue.
To enable it, open Settings and find the Semantic search section. It shows two provider cards above the Enable button: OpenAI, preselected and recommended, which embeds through OpenAI’s API under your own key and sends note text and queries to OpenAI; or Local, which runs a small model on this device. With OpenAI you save a key in the API key row first. With Local, turning it on downloads two files, model.onnx and tokenizer.json, roughly 34 MB in total, from https://releases.jotura.io/models/bge-small-en-v1.5-int8/v1/ into <config>/jotura/models/bge-small-en-v1.5-int8/v1/. Both are SHA-256 verified. The section then shows the provider that built the index (OpenAI text-embedding-3-small (1536), say, or Local bge-small (384)), the runtime backend for local indexes (one of GPU (CUDA), GPU (DirectML), GPU (CoreML), CPU, or Unknown), and an index row reading <n> chunks · <n> pending with an estimated time while embedding runs (and · <n> skipped for notes parked after a failure, retried when their content changes or on re-index).
The index is built on your device with either provider, and in a vault that syncs its vectors are shared with your other devices as ciphertext so they do not embed the vault again. With Local nothing else is sent anywhere; with OpenAI note text and queries go to OpenAI under your own key, and nowhere else. The full comparison is in Search.
Re-index vault, in the same section, wipes the chunks and rebuilds them in the background. Turn off & delete index below it is destructive: it turns semantic search off for this vault on every device and deletes the local vector database along with its -wal and -shm sidecars. Your notes are untouched, and re-enabling rebuilds the index.
From the CLI, semantic and smart search exit 13 (SemanticUnavailable) when the prerequisites are not met:
jotura search "how did we decide on the queue design" --mode semantic
The error message names the specific missing prerequisite. The possibilities are that <vault>/.jotura/vectors.db does not exist, that the database’s enabled flag is not set, that no config directory can be resolved, that the embedding model files are absent from the path above, that the ONNX runtime cannot be resolved or fails to load, or, for an index built with OpenAI embeddings, that no API key is available or OpenAI rejected it. The two key messages are semantic index uses OpenAI embeddings; set OPENAI_API_KEY or add the key in the desktop app (Settings -> Semantic search) and OpenAI rejected the API key; update it in the desktop app or OPENAI_API_KEY.
Stale or missing results from the terminal are usually a limit rather than a bug. The CLI is query-only: it embeds your query but never indexes notes, so freshness equals the desktop app’s last indexing pass and a note you just wrote from the terminal is not semantically searchable until the app reindexes it. And --in does not work with semantic modes: combining it with --mode semantic or --mode smart exits 9 (InvalidArgs), because path scoping is keyword-only.
Enable stops with “add an OpenAI API key first”
OpenAI is selected in the semantic search section and no key is available, so Enable stays disabled with Add your OpenAI API key to continue. The app looks in two places: the key saved from the API key row, and the OPENAI_API_KEY environment variable. Either one satisfies the check. In a vault that syncs, a saved key is sealed into the vault and shared with your other devices; otherwise it stays on this device in <config>/jotura/openai.json (%LOCALAPPDATA%\jotura\openai.json on Windows).
Paste your key into the API key row and click Save. The key is masked as you type, checked for shape (it must start with sk-, be at least 20 characters, and contain no whitespace) before anything leaves the machine, verified with one small embedding request against the model you selected, and then written to disk with owner-only permissions. Click Enable again once the row shows the last four characters. If you would rather send nothing to OpenAI, pick Local in the chooser and no key is needed.
The same check runs when you switch an enabled vault to OpenAI, and when you click Re-embed now or Re-index vault while Settings say OpenAI.
OpenAI rejected the API key
Indexing stops and the semantic search section shows OpenAI rejected the API key. That message means OpenAI answered a request with 401 or 403: the key was revoked or rotated, its project lost access to the embeddings endpoint, or the wrong key was pasted. Jotura treats it as a hard stop rather than a retry, so no further note text is sent until you act. Searching in Semantic or Smart mode fails with the same message, and the CLI exits 13 with OpenAI rejected the API key; update it in the desktop app or OPENAI_API_KEY.
To fix it, click Replace in the API key row and save a working key. The new key goes through the same format check and single verification request as the first one, and the queue resumes where it stopped: nothing already embedded is redone. If the key came from OPENAI_API_KEY in the app’s environment, the row says so; update the variable and restart the app, because the environment wins over the saved file.
If you do not have a working key to hand, pick Local in the provider cards. That re-embeds the vault on the local model after one confirmation and needs no key at all.
Embedding is paused after a network error
The Index row reports that embedding is paused and the Last error row shows a connection error, an HTTP 429 rate limit, or an HTTP 5xx from OpenAI. Nothing is broken. Each request already retried on its own, up to six attempts for a rate limit and four for a server or connection error, waiting as long as OpenAI asked or for a randomised backoff of up to a minute; when that ran out, the worker stopped the pass and scheduled another. It retries after 30 seconds, then 2 minutes, then every 10 minutes, and it also wakes as soon as any note changes. Notes waiting in the queue stay pending rather than being marked skipped, so nothing needs to be re-queued by hand.
While OpenAI is unreachable or rate limiting, a search that needs it to embed the query answers with keyword results instead, and Semantic mode says Search by meaning is busy. Showing keyword results. Keyword search is unaffected. If you are offline for a while and want search to keep working by meaning, the local provider is the one that never needs the network after its model download.
“Index was built with X; settings now say Y”
The semantic search section shows Index was built with <old>; settings now say <new> above a Re-embed now button. The provider, model, or size in Settings differs from what built this vault’s index. Search keeps working on the old index in the meantime.
The choice belongs to the vault: it is stored in the vault’s Jotura/settings.json and syncs, so a change made on one device re-indexes every device. The message appears when this device could not follow such a change on its own, most often because OpenAI is selected and no key is available here.
Click Re-embed now to delete the index and rebuild it with the current choice, after the same confirmation as a switch. If the current choice is OpenAI, that sends every chunk of every note to OpenAI and costs money, so check the model and size first. If you would rather keep the old index, change Settings back to match what the message says the index was built with. An index built before providers existed is treated as local and is never rebuilt until you choose something explicitly.
Images in my notes are blank
Web images do not load by default. The Load remote images setting under Settings then Editor is off, because fetching a web image tells its host your IP address and the fact that you are reading that note. Turn it on if you trust the sources in your vault.
Images stored inside the vault are unaffected by that setting and always render.
Separately, an .html note that “does not work” is usually behaving as designed. Jotura renders HTML files inside a fully sandboxed frame with scripts disabled, so anything interactive in the file is inert on purpose.
“Vault not found” after signing in
This error comes from the server, and it almost always means the app is pointed at a different API environment than the one the vault was registered against. A vault registered on one environment does not exist on the other.
Installed release builds default to https://api.jotura.io and need no configuration. Two things can move them off it.
The JOTURA_API_URL environment variable overrides the default at resolution time. It is never persisted, so unsetting it and restarting restores the default. The apiBaseUrl field in settings.json also carries the value, and it has no control in the Settings UI. A settings file still holding the old staging URL is migrated to the production URL automatically on load.
If you have set JOTURA_API_URL for testing, clear it and restart before investigating anything else. If you work across two environments, use a separate scratch vault for each rather than repointing one vault.
The installer is blocked on first launch
Jotura’s macOS builds are ad-hoc signed rather than signed with a paid Apple Developer identity, so Gatekeeper refuses the first launch. The lack of signed and notarized distribution is a known gap.
Running the app straight from the mounted disk image fails in a confusing way: file dialogs and configuration writes are blocked, so the window may never appear, or may appear empty. The reliable sequence is:
- Open the
.dmgand drag Jotura into your Applications folder, replacing any earlier version. - Strip the quarantine attribute from the copy you just made.
- Eject the disk image.
- Launch Jotura from Applications.
Step 2 is this command:
xattr -cr /Applications/Jotura.app
If Gatekeeper still refuses, right-click the app in Applications and choose Open, which offers a confirmation the plain double-click does not. Subsequent launches work normally either way.
To check which version installed, compare the app’s own About section in Settings, which reads Version <n>, against the bundle on disk:
defaults read /Applications/Jotura.app/Contents/Info.plist CFBundleShortVersionString
Windows has the equivalent problem for the same reason. The installer is unsigned, so SmartScreen warns that it comes from an unrecognized publisher and you have to choose to run it anyway. A Windows code-signing certificate is deferred alongside the Apple one.
The jotura command is not found
The CLI ships inside every desktop installer, but how it reaches your PATH differs per platform.
| Platform | Where the CLI lands | How it gets on PATH |
|---|---|---|
| macOS app bundle | Jotura.app/Contents/MacOS/jotura |
Open Settings, expand Advanced, then Command-line tool, and click Install. This symlinks /usr/local/bin/jotura and prompts once for an administrator password. |
| Windows MSI | The install directory | The MSI adds that directory to the system PATH during installation. Open a new terminal afterwards, since existing ones keep the old environment. |
Linux .deb |
/usr/bin/jotura |
Automatic. No action needed. |
| Linux AppImage | Inside the AppImage | First launch offers to copy the binary to ~/.local/bin/jotura. That directory must already be on your PATH for the command to resolve. |
The Command-line tool card appears on macOS, Windows, and the Linux AppImage. The .deb build has no card, because its CLI is already on PATH.
The startup install prompt, which appears on Windows and the Linux AppImage only, records that it asked regardless of whether you said yes or no. It never asks a second time and there is no way to reset it from Settings, so install from the Settings card instead if you dismissed it.
The other sharp edge is a Windows failure message, Could not update PATH. That comes from the in-app Command-line tool card, which is a different mechanism from the MSI’s. The card writes the user PATH through PowerShell ([Environment]::SetEnvironmentVariable) rather than setx, because setx truncates PATH beyond 1024 characters, and then broadcasts a settings-changed message so new processes pick it up. That message therefore says nothing about the MSI’s system PATH entry.
To remove the macOS symlink later:
sudo rm /usr/local/bin/jotura
To confirm which binary the shell resolves, and whether its directory is on PATH:
jotura doctor
The cli-on-path check prints the current executable path and whether its directory is on $PATH. Version skew is worth ruling out at the same time:
jotura --version
An installed CLI can lag the desktop app by a full release. The difference can go well beyond a missing flag: a whole command group can be absent from the older binary, and commands that do exist can emit different fields. Compare that output against the About section in Settings, and reinstall from the Command-line tool card if they disagree. Setup detail lives on Install and setup.
Edits from another device or another app
Jotura does not ask you to resolve content conflicts. The design is deliberate, and knowing how the merge works keeps its behavior from looking like a bug.
When a note open in the editor changes on disk, the app performs a silent three-way merge rather than prompting. The base is the tab’s last clean content, yours is the editor content, and theirs is the fresh copy from disk. It retries up to three times and announces Merged changes from disk when it succeeds. Sync pulls use the same merge, and neither path writes conflict markers into your text.
The merge keeps both versions of lines that differ substantially and collapses lines that are nearly identical, and that second rule is the one that surprises people. Two lines that share at least 80 percent of their bytes are treated as two edits of the same underlying line, a checkbox toggle or a fixed typo, and collapse to a single variant: the remote edit when your copy is unchanged from the base, and your edit when both sides changed it. So rewording the same sentence on two devices does discard one version. Lines that differ more than that, for example - buy milk against - buy eggs, both survive, and you end up with the two versions sitting next to each other rather than one of them disappearing.
Content that cannot be merged as text keeps the local bytes. That covers binary files, non-UTF-8 content, a delete racing a dirty edit, and a shape mismatch between the two sides. For file writes the sync row and base advance, so replaying the same operation is a no-op rather than a fresh conflict on every pull.
There is exactly one conflict prompt in the app: the banner reading This note was deleted or moved outside the app. with Close tab and Recreate with my version. That last option re-reads the disk hash and writes your version unconditionally. Full history and restore options are covered in Version history.
From the CLI, conflicts surface as exit codes rather than prompts. Exit 2 (HashConflict) means the --if-hash precondition did not match, and the error JSON carries a currentVsIntended unified diff. Exit 14 (TargetMissing) means the file was deleted or moved between your read and your write, and Jotura deliberately does not recreate it. The safe loop is always read, transform, then write with the hash:
HASH=$(jotura read notes/today.md --json | jq -r .hash)
jotura edit notes/today.md --replace "old" --with "new" --if-hash "$HASH"
Both the CLI and the desktop app serialize on a per-vault advisory lock held across the hash compare and the write, so two writers validating the same hash can never both succeed. The loser gets exit 2. See Editing notes.
Restoring something you deleted
Jotura’s Move to trash does not use the operating system trash. It moves the entry into <vault>/.jotura/trash/ alongside a .trashmeta sidecar recording the original path, size, and time, then announces Moved "<name>" to trash.
There is no trash browser in the desktop app. Listing, restoring, and permanently deleting trashed items are CLI-only today, and no hidden menu exposes them.
jotura trash list
That prints the original path and the time it was trashed, one per line, or a JSON array with --json. Restore by the item’s original path, not the path inside the trash directory:
jotura trash restore "notes/quarterly review.md"
To restore somewhere else instead:
jotura trash restore "notes/quarterly review.md" --rename-to "archive/quarterly review.md"
The share guard applies to the destination, so restoring into a share where your role is viewer exits 15 (ReadOnlyShare).
The remaining trash commands are permanent:
| Command | Effect | Notes |
|---|---|---|
jotura trash <path> |
Soft-delete a note. | Same as the app’s Move to trash. Refuses a share mount root. |
jotura trash purge <path> |
Permanently delete one trashed item. | Takes the original path. No confirmation flag, and like trash empty it does not route through the share guards. |
jotura trash empty --force |
Permanently delete everything in the trash. | --force is required. Without it the command exits 9. |
Note the asymmetry: trash empty demands --force and trash purge does not. Also note that jotura delete <path> is permanent by default and only soft-deletes when you pass --trash. Deleting a path that does not exist is a silent success on a real run, though --dry-run reads first and exits 3. Lifecycle detail is on Creating, moving, and deleting.
Settings appear to have reset themselves
Settings live in a single JSON file:
| Platform | Path |
|---|---|
| Windows | %APPDATA%\jotura\settings.json |
| macOS | ~/Library/Application Support/jotura/settings.json |
| Linux | ~/.config/jotura/settings.json |
The file is written atomically (a temporary file renamed into place), so a crash during a write leaves the old copy intact. When the file fails to parse anyway, for example after a hand edit, Jotura starts on built-in defaults rather than refusing to start, and keeps the unreadable copy beside it as settings.json.corrupt before it writes anything. That can present as every preference reverting at once.
Many settings belong to the vault rather than the device. Those live in the vault’s Jotura/settings.json and sync, so a change made on another device, or opening a different vault, can change them. The Settings UI applies a change immediately and puts it back if the write is refused.
Adding a setting is backward compatible: missing fields fall back individually rather than resetting the whole file. The full list of controls is on the Settings reference.
Imported documents failed to convert
Imported documents (PDF, Word, spreadsheets, and similar) are converted to searchable markdown by a bundled converter. Open Settings, expand Advanced, then Document conversion to see the state. The badge reads Unavailable when the converter is missing entirely, Blocked when the converter is present but cannot currently run, otherwise <n> failed.
Blocked is the case worth reading closely, because it fails every document at once rather than a few. The converter only runs inside an operating system sandbox, and when that sandbox cannot be established Jotura refuses to convert rather than parsing untrusted files unprotected. A Conversion blocked row spells out what the sandbox reported. Every generated note also carries the reason in its body, and the diagnostics log records which sandbox profile was tried and how long it took.
Four actions sit in that section: Reconvert stale documents, Reconvert all documents, Convert pending documents, and Retry failed conversions. The helper text under the last one is honest about the usual cause: failures are usually corrupt or password-protected files.
From the CLI, retry only the entries that are out of date:
jotura regenerate-md-store --stale-only
On macOS, the very first conversion after installing or updating Jotura takes about fifteen seconds while the operating system checks the converter’s signature. After that a typical document converts in about half a second.
Each of those four actions re-checks the converter and its sandbox from scratch rather than reusing an earlier verdict, so a blocked converter that has since been fixed recovers without restarting the app.
Each result carries a reason of Missing, SourceChanged, ConverterUpgraded, or PreviousFailure. Be aware that --stale-only and --all take precedence over any explicit paths you also pass, and those paths are silently ignored rather than warned about.
Doctor covers this area with markitdown-sidecar and markitdown-sandbox. The first reports the converter’s version, or says it was not found next to the CLI and that document conversion produces stubs until it is installed. The second reports fail when no usable operating system sandbox is available, which blocks conversion entirely, and warn when conversion is running unsandboxed by explicit opt-in.
There is a setting for the no-sandbox case, off by default: Allow unsandboxed conversion, under the same Advanced group. Leave it off unless you trust every document you import, because the alternative is parsing untrusted files with your full account privileges. The environment variables JOTURA_DISABLE_SANDBOX and JOTURA_ALLOW_UNSANDBOXED_CONVERSION have the same effect for CLI runs, and doctor surfaces either one as a warning. More on the import pipeline in Documents and Documents from the CLI.
Making sense of a CLI exit code
Every exit code the jotura command line tool returns, with the extra fields each error carries, is listed in Exit codes and JSON output.
Where to go next
If none of the above matches what you are seeing, run jotura doctor and copy the diagnostics bundle from Settings before reporting the problem. Between them they capture the vault path, sync and share state, document conversion counts, semantic index status, and the recent log lines, which is almost everything needed to reproduce a report. Nothing in either is transmitted anywhere, so you decide what to paste and where.
If you are new to the app and something is not where you expect it, Getting started and Keyboard shortcuts cover the ground this page assumes.