Skip to content

Latest commit

 

History

History
196 lines (146 loc) · 9.63 KB

obsidian-version-support.md

File metadata and controls

196 lines (146 loc) · 9.63 KB

Which Obsidian version does Markport run?

Written in English on purpose, unlike most of docs/ - the in-app failure message points users here, and not all of them read Hebrew.

Short answer: Obsidian 1.12.7. That is the newest version Markport runs, and the pin is deliberate - not a lag in maintenance.

If you point it at 1.13 or newer

The app will not start. You'll see this instead of the loading spinner:

Obsidian 1.13.4 did not start. This version asks its host for a startup acknowledgement that Markport does not provide. The newest version known to work here is 1.12.7 - see the README.

Nothing is broken on your side and there is nothing to fix. Run node scripts/update-obsidian-mobile.js --version 1.12.7 to go back.

What changed in 1.13

Obsidian 1.13 added a check at startup. Before it initialises, it asks the host environment for a terms field, and expects the exact text of an acknowledgement stating that the user may not distribute the Obsidian application in any form without explicit approval from the Obsidian team.

The channel is platform-dependent, and the acknowledgement sits on both sides - the host supplies it, the renderer compares it against its own copy. Measured 2026-09-05 against both 1.13.4 builds.

Channel If it doesn't match
Mobile bundle (what Markport runs) App.getInfo().terms (Capacitor) throw new Error
Desktop bundle ipcRenderer.sendSync("terms") (Electron IPC) window.close()

Reconstructed, not copied: the shipped builds are minified, and the mobile one is TypeScript's ES5 downlevel - a generator state machine. Names and shape are ours; the control flow and the channels are theirs. The acknowledgement itself is elided.

The comparing side. Mobile - assets/public/app.js inside Obsidian-1.13.4.apk. App is Capacitor's App plugin:

const appInfo = await App.getInfo();
Platform.build   = appInfo.build;
Platform.version = appInfo.version;

let managedPolicy = "";
try {
  managedPolicy = (await App.getManagedPolicy()).value;
} catch {
  // swallowed - getManagedPolicy is not fatal
}
if (managedPolicy) freezePolicyOnce(parsePolicy(managedPolicy));

if ("I understand and agree that I am not allowed to … granted by the Obsidian team." !== appInfo.terms) throw new Error();

getManagedPolicy() sits inside a try whose catch arm swallows, so it is not fatal. The terms comparison sits outside it - that is what makes it the only hard blocker.

Desktop renderer, inside obsidian-1.13.4.asar - the same value read over IPC, in the same run as version and policy:

Platform.version = electron.ipcRenderer.sendSync("version");
Platform.build   = electron.remote.app.getVersion();
freezePolicyOnce(electron.ipcRenderer.sendSync("policy"));

const os = requireNode("os");
if (os) {
  Platform.deviceName = os.hostname();
  Platform.osName     = os.version();
  Platform.osVersion  = os.release();
}

if ("I understand and agree that I am not allowed to … granted by the Obsidian team." !== electron.ipcRenderer.sendSync("terms")) return window.close();

The supplying side. The Electron main process, same asar - the constant, and the handler chain it is registered in. This one is quoted close to as-shipped; the desktop build targets a modern Chromium and is not downlevelled:

const ACKNOWLEDGEMENT = "I understand and agree that I am not allowed to … granted by the Obsidian team.";
…
electron.ipcMain.on("terms",         e => { e.returnValue = ACKNOWLEDGEMENT }),
electron.ipcMain.on("is-quitting",   e => { e.returnValue = isQuitting }),
electron.ipcMain.on("is-closing",    e => { const win = electron.BrowserWindow.fromWebContents(e.sender);
                                            e.returnValue = isQuitting || !!(win != null && win.closing) }),
electron.ipcMain.on("desktop-dir",   e => { e.returnValue = desktopDir }),
electron.ipcMain.on("documents-dir", e => { e.returnValue = documentsDir }),
electron.ipcMain.on("resources",     e => { e.returnValue = resourcesPath }),
electron.ipcMain.on("version",       e => { e.returnValue = appVersion }),

terms sits between is-quitting and is-closing, answered exactly like version or resources. On Android the same string is compiled into classes.dex of the 1.13.4 APK (found with grep -a; not in classes2.dex) and reaches the renderer through Capacitor.

So the acknowledgement is not something the user types or accepts anywhere. It is a constant that ships inside Obsidian's own client, on both halves of it.

On a real Android device, Obsidian's own native layer supplies it. Markport replaces that native layer with browser shims, and those shims don't supply it - capacitor-shim.js ships terms empty, and shims/electron.js answers the terms IPC channel with an empty string for the same reason.

The other thing 1.13 added: managed policy

getManagedPolicy() in the excerpt above is not part of the gate, but it is worth knowing about. It is new in 1.13 - 1.12.7 does not call it at all - and it reads an enterprise MDM configuration: a JSON string with eight keys.

{ plugins, themes, snippets, sync, publish, webViewer, devTools, insider }

Two different parsers. snippets, sync, publish, webViewer, devTools and insider are strict booleans - only a literal true permits, anything else denies. plugins and themes accept either true or a non-empty array, which is kept as-is and used as an allow-list of ids. The default, when no policy is supplied, is everything permitted; the object is Object.freezed and only the first value wins.

Markport's shims do not implement getManagedPolicy at all. On 1.12.7 that is moot - nothing calls it. On 1.13+ the call would reject, Obsidian's own try would swallow it, and the app would run with the permissive default. So an admin-supplied policy would not reach a Markport instance even if one were configured on the device. Nothing here is a workaround: it is simply not implemented, and it is not what stops 1.13 from starting.

Why we don't work around it

We could. The shim could return the string and the app would boot.

We don't, because the check isn't a bug or an API change we failed to keep up with - it's a control Obsidian added deliberately, and its content is a statement about redistribution rights. Having this project's code assert that acknowledgement on a user's behalf in order to get past it is not something we're willing to ship. Obsidian is not open source; this is their application, and it's their call to make.

To be explicit about what is in the code: App.getInfo() passes through whatever value the person running the instance has provided in their own browser, and ships empty. Markport does not contain the acknowledgement text and does not supply it. Out of the box, on 1.13+, the app does not start - which is the intended behaviour.

If you choose to run a newer version anyway

You are an adult with your own copy of Obsidian, and what you do on your own machine is your decision. But be clear with yourself about what that decision is:

Supplying that value is you making the declaration, personally. Not this project, and not whoever wrote the code - you. The acknowledgement is a statement that you understand you may not distribute the Obsidian application in any form without explicit approval from the Obsidian team, and that Obsidian is a registered trademark you may not use without their permission. If you supply it, you are asserting that, and you are responsible for actually complying with it.

Two things follow, and they are not the same:

  • Running it privately, for yourself, against your own vault - you are the only person involved, and the declaration is one you can honestly make.
  • Serving it to anyone else - a public deployment, a shared link, a hosted instance for a team
    • is exactly what the acknowledgement says requires explicit approval. Do not do it on the strength of a flag you set in your own browser.

This project takes no position on your private use and provides no support for it. If it breaks, that's yours too: the pin exists because 1.12.7 is what we test against, and nothing past the gate has been verified on newer versions.

What we measured before deciding

This matters, because "it doesn't work on 1.13" could easily have meant our shim layer had fallen behind. It hasn't. Measured 2026-08-02, comparing 1.12.7 and 1.13.4:

1.12.7 1.13.4
Boots in the browser Yes - vault chooser renders fully No - stops during startup
app.js runtime errors 0 4
native-bridge.js (the Capacitor bridge) - byte-for-byte identical
Capacitor plugins referenced 11 the same 11
Startup acknowledgement gates - exactly one
Our test suites pass pass

The download-and-extract pipeline runs cleanly on 1.13.4, the shim contract didn't move, and no new plugin surface appeared. getManagedPolicy() is new but wrapped in a try/catch inside Obsidian's own code, so it isn't fatal.

Note this is static analysis plus a boot attempt - it establishes that the acknowledgement gate is the only hard blocker, not that everything past it would behave correctly.

What would change this

  • Obsidian granting explicit permission for this use, which we'd rather ask for than route around.
  • A future Obsidian release where the check no longer applies to this scenario.

Until then the pin stands. If you're reading this because you wanted a newer Obsidian in a browser: that's a fair thing to want, and the place to raise it is with the Obsidian team.

Related

  • AGENTS.md - "Before you touch the bundle", and the zero-patches policy
  • src/client-mobile/boot.js - the boot watchdog that produces the message above