v0.63.0
Released Aug 25, 2026.
Minor Changes
Edits typed while something else is rewriting the same document no longer disappear. The bridge between the visual editor and the raw markdown behind it gained four safety nets, all default-on: a defer guard that holds back a rebuild which would overwrite a keystroke that has not propagated yet, a pre-drain flush that pushes a pending non-overlapping keystroke into the document before an agent write or undo rebuilds it, a loss detector that spots content dropped at the reconciliation boundary, and a fixed-point backstop that freezes a rebuild loop which oscillates instead of settling. The practical effect: typing through an agent write, a
git pullthat lands on the open document, or a same-second edit from a second window is far less likely to cost you a line.- Anything a safety net cannot save in place is written to a recovery checkpoint, and those checkpoints now appear in the Timeline as ordinary "Recovered content" versions you can preview and restore like any older version. Before this release the same checkpoints were written but reachable through no API and invisible in the UI, so rescued content was effectively lost anyway.
- Undo in source mode is now a single origin-aware system. Cmd/Ctrl-Z undoes only what you typed, never a collaborator's or an agent's write. Previously CodeMirror's own history could revert content you never wrote.
- Unsynced work survives more of the browser lifecycle: a tab flushes to the server when you switch away and resyncs when you come back, edits captured during a server-instance recycle land in a durable IndexedDB outbox so a tab crash mid-recycle no longer drops them, and a worker-homed presence heartbeat keeps a backgrounded tab from being pruned as offline by its peers. In the desktop app, a window holding unsynced work opts out of Chromium's background throttling until it is clean.
- New project config leaves, each a kill-switch defaulting to on:
bridge.deferGuard.enabled,bridge.preDrain.enabled,bridge.lossDetector.enabled,bridge.fixedPoint.enabled,bridge.flushOnHide.enabled,bridge.backgroundThrottle.enabled. Turn one off only to isolate a suspected regression. - New
lossCapture.enabledandlossCapture.maxBytesconfig leaves plus a local event ring at.ok/local/loss-capture/(roughly 12 MB before it rotates). It records which safety net fired, on which document, and when, and it is content-free by schema: a byte length and a digest, never document text. Nothing leaves your machine until you runok diagnose bundle; setlossCapture.enabled: falsefor a sensitive workspace.
Stop recovery-checkpoint cleanup from discarding the newest rescue point. Checkpoints written in the same second could not be ordered by their timestamps, so the sweep picked which one to drop essentially at random and could keep an older copy while permanently deleting the most recent one. Because loss-hardening writes one checkpoint per open document in a burst, the discarded copy could be a document's only recovery point. Cleanup now keeps every checkpoint it cannot confidently order, so it may hold slightly more than the retention budget rather than destroy the wrong one.
Correct the Source editor entry in the keyboard shortcut help. It advertised undo-selection shortcuts that source mode does not have; those shortcuts still work in the code block editor and in code-shaped property fields, and are now listed there instead. Redo shortcuts are unchanged and continue to work on every platform.
ok diagnose bundlenow redacts by default, and the y/N prompt states plainly what leaves your machine before it writes anything.Credentials are scrubbed on every bundle. GitHub PATs, AWS access keys, Anthropic and OpenAI keys, bearer tokens, JWTs, and URL-embedded credentials are replaced with
[REDACTED-*]placeholders, and macOS/Linux home-directory paths are anonymized to~/(the path hierarchy below the home directory is preserved).ok diagnose bundlenever ran this scrub before, so bundles produced by earlier versions could carry a live token;ok bug-reportand the in-app bug report already did.The content directory path is masked as
<CONTENT_DIR>, so a shared bundle does not leak your home-directory layout.The flag is now
--no-redact, an explicit opt-out that writes a raw bundle for you to inspect locally: no credential scrubbing and no path masking.--redactis still accepted and is now simply the default, so existing scripts and muscle memory keep working.Document names ship in cleartext. The old opt-in
--redactpass hashed them todoc:<8hex>and wrote a<bundle>.docnames.jsoninverse-map sidecar next to the zip; both are gone. Legible document names are what make a bundle diagnosable, so the tradeoff is now an informed one rather than a silent one: the consent summary says "in cleartext" next to the count, and the manifest'sredactionblock no longer carriesdocNameMapSidecarordocNameCollisions.The consent summary is now itemized by what actually leaves the machine: document names, whether the content directory path is masked, and whether credentials were scrubbed (with the line count). Under
--no-redactit says so in as many words.Bundles carry the content-loss ring as
state/loss-current.jsonlandstate/loss-prev.jsonlwhen it exists, so a "my edit vanished" report arrives with the evidence attached. The ring is content-free by schema.The desktop app now scrubs captured renderer console output before writing it to
~/.ok/logs. Console capture is on by default, and a credential printed to the console previously landed verbatim in that log file for its 7-day retention — the keyed-field redaction in front of the log never inspected the message text. The same scrub also masks/Users/<name>/paths to~/there, matching what the web build already did on its way to the server log.
The in-app bug report and
ok bug-reportalways redact.ok diagnose bundleis the only surface with an opt-out.GitHub-style
<div align="center">wrappers now render the way GitHub shows them: the tag lines disappear and everything between them — headings, text, badge images — actually centers (left/right/justify work too). The content inside stays fully editable, and the<div align>markdown is preserved byte-for-byte on disk. Divs with other attributes keep their literal rendering.Documents no longer get permanently stuck offline after the server restarts. Previously, if the restart took more than a couple of seconds, an open document could land in a state where it reconnected forever without ever syncing: the banner said "Connection lost, keep this tab open, your edits will sync when reconnected", but nothing it promised was going to happen. Switching to another document and back did not help, and anything typed while stuck was lost on reload. The client now treats a restarted server the same way whichever way it finds out about it, so the affected documents recover on their own and the edits typed during the outage are replayed instead of dropped.
Git operations now honor the GitHub account you've already declared for a repo, instead of always using whichever GitHub CLI account happens to be active. If the remote URL carries a username (
https://alice@github.com/owner/repo) or your git config has acredential.<url>.usernameentry (HTTPS remotes only), sync pushes, push-permission checks, andok cloneall authenticate as that account — read from standard git mechanisms, in the same order git itself resolves them, with no new OpenKnowledge configuration. Multiple accounts on one machine are a GitHub CLI feature, so this needs each account signed in withgh auth login(GitHub CLI 2.40 or newer); OpenKnowledge's own GitHub connection stores one account per host, so a declaration changes the error messages you see but not which credentials get used unless the account is also signed in to the GitHub CLI. If the declared account isn't signed in there, OpenKnowledge falls back to the credentials it does have rather than losing them, and picks up a latergh auth loginfix within a sync cycle or two.Error messages now tell the truth about identity: access-denied messages end with the account that was actually used ("Authenticated as bob."), a missed declaration is named ("Your remote URL names alice, but that account's credentials couldn't be used."), and "repository not found" failures no longer offer sign-in as the fix — the repo may not exist, or the account used may not be able to see it, and neither git nor GitHub reveals which.
Behavior change for remotes that embed a username. A remote like
https://alice@github.com/owner/repois now recognized as a GitHub origin, so OpenKnowledge's own credential flow takes precedence over ambient system credential helpers for it — as it always has for other GitHub remotes. If such a remote's only working credential lived in Git Credential Manager or your OS keychain, sync will report you as signed out after upgrading: connect GitHub in OpenKnowledge once (or sign in to the GitHub CLI) and it resumes. Two smaller effects of the same recognition: public repos with these URLs clone anonymously without requiring sign-in, and receiving a shared folder accepts them instead of asking you to switch the remote.One file on disk can no longer be backed by two separate collaboration documents. Addressing a document by a name that kept its
.mdor.mdxextension used to open a second, independent room over the same file. Both rooms then wrote to that file with no merge and no conflict shown, so whichever saved last silently overwrote the other, and an affected editor tab restored the broken state on every launch. Extension-qualified names now collapse onto the document they actually name, at the one point a room is keyed, so every surface converges: wiki links, graph nodes, share links, skill references, the preview URL, and the HTTP document routes. When a.mdand an.mdxfile genuinely exist side by side under one name, the document API still reaches each of them: the extension-qualified name serves the file the bare name does not. The editor lists such a pair as a single entry, so only one half opens there, and that is unchanged. Tabs carrying the old shape are repaired when they are read, so an affected install fixes itself on upgrade.ok opennow prints the project it resolved. Before, a path that landed in a different project than you expected looked identical to one that landed where you meant, and the only way to tell was to runok psafterwards. When the resolved project sits inside another OpenKnowledge project, the output names both.--projectis honored wherever you put it. Previously it worked only when it came before the target and the target had no file extension. In every other shape it was silently dropped and the project was derived from the file instead. When an override cannot be honored the command now fails and says why, rather than quietly opening somewhere else.ok stopdeclines to stop a server that something is still connected to, naming how many clients are attached, and--forceoverrides it. This counts every kind of client, including agents, because stopping a server an agent is writing through loses work the same way. A server that cannot be reached is stopped without the check.ok deinitandok uninstalldo not gain a second prompt: they add a line to the plan they already show, noting that restarting will not bring those windows back.- A window whose server has stopped now offers to restart it from the file sidebar, which is the surface that reports the failure. The action already existed but only appeared in a transient toast and in the document error state, so it was easy to miss and the window read as permanently broken.
ok stoprecords what it stopped, and servers record why they exited. Neither left a durable trace before, so an external stop, an idle shutdown, and a crash were indistinguishable afterwards and none of them reached a diagnostic bundle. Log records also name the project actually resolved instead of a placeholder, andok stopreports its target rather than the directory you happened to be in.- A project nested inside another project is no longer indexed by its parent. The two used to share content and reconcile only through disk writes.
runStop, exported for programmatic use, is now async and refuses by default when clients are connected. Callers must await it, and passforce: truefor the previous unconditional behavior.
Serve your knowledge base's MCP endpoint to your other devices through any HTTPS tunnel — ngrok, Cloudflare Tunnel, Tailscale, or a reverse proxy: run the tunnel against the stable remote port (default 24550,
remote.portto change) and start the server withok start --remote <url>(or setremote.urlin.ok/config.ymland pass a bare--remote). The model is trust-the-tunnel: OpenKnowledge does not authenticate remote callers — anyone who can reach the tunnel URL has full read-write access, and restricting reach is the tunnel's job (private tailnet, ngrok OAuth, Cloudflare Access). The trade is always explicit: remote access is armed only by the--remoteflag (never by config alone), every remote start prints a warning banner, and a server started without the flag refuses tunneled requests with a fix-it hint instead of serving them. The web UI is served from the same tunnel URL (/= editor,/mcp= MCP — one port, one tunnel; browse and edit from your phone). Its collaborative surfaces work over the tunnel too: live editing and the Ask AI panel, which runs an agent on the machine hosting the server (using that host's own agent and credentials). Wrong-Host requests (DNS rebinding) are refused everywhere. Local loopback behavior is unchanged.Report which checks actually ran. Every successful
ok lint/ok auditreport, HTTP response, and MCPlint/auditresult now carriesran, naming the source families selected for that call, so a clean result is no longer indistinguishable from one where the check you cared about was switched off. A family absent fromranwas not checked, and[]means nothing was.A selected check that fails stays in
ranand is explained inwarnings, and the summary line says the run could not fully complete rather than reporting clean. OnPOST /api/lint/fix, the singularwarningfield is deprecated: readwarnings, which now carries the same post-write re-lint failure alongside plugin failures.The slash menu now has an Emoji entry — type
/emoji(or just/:) and an emoji picker opens right at your cursor, with search. Picking one inserts it as plain text, so it round-trips through markdown untouched.Git sync gets a rebuilt control panel and mode-independent manual actions.
The sync popover now carries a three-way mode selector — Manual, Auto (Pull only), Auto (Pull and Push) — plus manual Pull, Push, and Pull and Push buttons that behave identically in every mode: the mode only chooses what runs on a schedule. Pull never commits your in-progress work in any mode; Pull and Push runs the classic sync (commit, push, merge). The panel's status section lists which files a pull would bring in, which files a push would include, and which changed files sit outside OpenKnowledge's commit scope and would be skipped — refreshed by a read-only fetch when the panel opens. Settings and the enable dialogs use the same mode names.
How often the automatic modes run is now configurable per machine: Settings → Sync → Advanced adds Check for updates every and Push my edits every (30 seconds to 1 hour), stored as
autoSync.pullIntervalSecondsandautoSync.pushIntervalSecondsin the project-local config. When a pull is blocked because your uncommitted local changes overlap an incoming update, the popover now lists the blocked files under Changed here and on the remote with a Commit and sync button that commits exactly those files and resumes, plus a Resolve in terminal handoff (desktop app) for stashing or discarding by hand — OpenKnowledge never discards uncommitted work for you.Two fixes to what a pull does with files it cannot merge cleanly. When an incoming change collides with a local edit to a non-document artifact, Pull now keeps your version rather than failing the whole pull — the Pull verb's disposition is keep-mine, and previously a single dirty artifact overlapping an incoming change would abort everything else the pull had to deliver. Separately, sync's own shareable files under
.ok/(the ones it legitimately writes) are no longer refused by the symlink guard's state-directory check, which had been rejecting them by resolved path.Three smaller changes ride along. The panel's freshness line now reports each direction separately (
↓ 2m ago · ↑ 5m ago) instead of one "Updated" timestamp that only ever reflected whichever direction ran last. An Advanced settings link in the popover opens Settings → Sync with the Advanced section already expanded. And when a new worktree inherits its sync setting from the root project, the notice names the mode exactly as the Settings control labels it, rather than using retired wording.Enter inside a table now works the way it does in Notion and Obsidian: it moves the cursor to the same column of the row below, and from the last row it creates a new row — so you can fill a table top to bottom without touching the mouse. Enter no longer splits a cell into multiple paragraphs (which markdown tables can't represent and used to resolve unpredictably); Shift+Enter is the way to get a line break inside a cell.
Patch Changes
Agents started from the desktop app now launch even when their CLI was installed through a JavaScript package manager's global bin (pnpm, bun, Volta, Yarn, or npm's user prefix). A desktop app opened from the Dock or Finder inherits a minimal
PATHthat omits locations like~/Library/pnpmand~/.bun/bin, so an agent such as Pi that ran fine in a terminal would fail to start with a "command not found" error. Agent and terminal launches now append the well-known tool directories and package-manager global bins toPATH— the same approach already used for git — so these agents start correctly. When a command still can't be found, the error now explains that Dock/Finder-launched apps don't see your shell'sPATH.You can now queue messages to an in-app agent while it's still working. Sending mid-turn no longer fails with "a turn is already running" — the message joins a queue and is sent automatically as soon as the current turn ends, in the order you wrote them. Queued messages appear between the transcript and the composer, where each one can be edited in place or removed before it runs. Stopping the agent clears the queue, and it's never saved to disk, so a crash mid-turn can't resurrect messages you never sent. Up to 20 messages can be queued at once.
In-app agent (ACP) threads: four improvements.
- Numbered lists in agent replies no longer render off-screen. Ordered- and unordered-list markers were being clipped off the left edge of the narrow thread panel; lists now get inline-start padding so their markers stay on screen.
- Agent settings persist per agent type. Picking a model, reasoning effort, or agent mode in a thread is remembered for that agent, and the next thread you start with the same agent opens on those settings — applied before the first turn (model first, so dependent options like thought level re-validate).
- A mode that lets the agent act without asking is marked. Because modes carry across threads, a permissive one — Claude's Bypass Permissions and Accept Edits, Codex's Agent (full access), Gemini's YOLO and Auto Edit, goose's Auto — puts a small amber dot on the settings button, with a hover tooltip spelling out what it allows. (Cursor exposes no permissive mode over ACP, and goose's Smart Approve still prompts for risky steps, so neither is flagged.) The marking follows whichever mode is actually in force, whether you just picked it or it carried over from your last thread. It's a recognition heuristic over the mode's name, so it's a hint, not a guarantee — it never blocks or changes a mode.
- Thread transcripts are stored globally in
~/.ok/threads/(alongside the existing~/.ok/acp-agentsand~/.ok/runtimes) instead of per-project, so they survive project-folder moves. Each project still sees only its own threads (matched by working directory). Threads created before this change are read back from their old per-project location — nothing to migrate. Per-project tool-permission grants are unchanged.
Bug reports sent from the desktop app now carry the evidence needed to diagnose an editor crash. Two pieces were missing. First, the detailed-diagnostics report is meant to be a superset of the basic one, but it was leaving out the app's own log file — the one the desktop app writes the editor's console messages into. Basic reports included it, detailed ones did not, so the most thorough report was the one that dropped the crash trail. Second, when the editor hit an error, the report recorded the error message but not React's component stack, which is the only part of a crash that still names real parts of the app in a released build; without it, a crash report read as a string of meaningless letters and numbers. Reports now include both, so a crash can be traced without asking you to copy anything out of a developer console by hand. Secrets in the newly included log are redacted the same way as everywhere else, and the component stack is trimmed to its most relevant frames.
Route the Vite development server through the same collaboration WebSocket host as
ok start, so/collab,/collab/keepalive, and/collab/threadshare admission, message-size, presence-cleanup, and shutdown behavior. This removes the duplicated upgrade stack that could drift between development and production.Comment markers in the right-hand rail no longer sit on top of the toolbar buttons in the editor's top right corner. The rail ran the full height of the editor's scroll area, but that area reaches up underneath the toolbar, so any comment whose passage was scrolled above the visible text parked its icon at the very top of the rail, in the same corner as the panel and action buttons. With several such comments they stacked down over that corner. The rail now starts below the toolbar, and a comment anchored to a line hidden behind the toolbar dims like any other off-screen one instead of being drawn over it.
The Default tile in Settings → Plugins → Themes now previews its own colors instead of borrowing the palette you currently have selected. Picking Dracula used to repaint the Default preview in Dracula's colors, so the grid gave you no way to see what switching back would actually look like. The Default tile now shows the app's own light or dark palette, following your light/dark preference, whichever theme is active.
Fix the editor's main content being squeezed into the top of the pane with a large empty band below it. The bottom sessions dock could be left holding space while hidden and showing nothing, and its drag handle is disabled while hidden, so there was no way to reclaim the space. The dock now collapses whenever it reports a size while hidden, so that state can no longer persist, and it records a diagnostic when it has to repair itself so a recurrence leaves a trace. Separately, the dock's height is re-clamped to at most half the window whenever the window is resized — previously a height chosen on one display kept its old ceiling after a move to a smaller one.
Reopening a single-file window (
ok open <file>) after its background server has stopped now starts a fresh, live editing session instead of silently reconnecting to the dead one. The desktop app remembers an open single-file window by the file's path, but it was only checking that the window was still on screen, not that the server behind it was still running. So if that server had exited (a crash, an idle shutdown, or a manual stop) while the window stayed open, opening the same file again reported success but left you with an editor that could never connect. The app now checks that the server is actually alive before reusing a window, and it also notices when a single-file server exits on its own and retires the stale session so the next open starts clean.Prevent fullscreen terminal applications from gaining an erroneous scrollbar after the terminal is resized.
Creating a folder from the sidebar, or renaming one while you have it open, no longer closes its tab and drops you back on the empty workspace screen. A folder tab was kept only while its folder appeared in the server's folder listing, and an empty folder does not reach that listing until the server has caught up. A background refresh landing inside that window closed the folder you had just navigated into, and because a freshly created folder is usually the only thing open, the workspace was left with nothing and fell back to the onboarding view. The folder you are currently viewing is now held open through that window, matching the protection documents already had, whether you reached it from the sidebar or from a wiki link. Folder tabs you have navigated away from are still tidied up as before, and deleting a folder yourself still closes its tab straight away.
One behavior worth knowing, because it is new: a folder deleted outside the app, by an agent or another client, keeps its tab open for as long as you are viewing that folder.
Frontmatter schema settings are now just the on/off toggle.
The Modified badge and the Reset-to-default button are gone. Both described the config file rather than the schema: "Modified" meant only "config.yml maps this file", on or off, and Reset removed that mapping. Neither is something you need a model of to decide whether a schema should validate your docs, and in practice the words did the opposite of explaining themselves. Reset in particular read as "refresh", so pressing it to reload the panel silently discarded every glob you had written. The Only modified filter went with them, since the concept it filtered on no longer appears anywhere in the surface. Search still narrows the list.
Your globs are always kept. Turning a schema off leaves its
appliesTopatterns in place, so turning it back on restores exactly what you had. The toggle never discards a mapping. If you want a schema out ofconfig.ymlentirely, edit the file — or delete the schema, which removes the file and its mapping together.Shorter delete confirmation. Deleting a schema no longer adds "Docs it validated keep their frontmatter; they just stop being checked." Nobody expected deleting a schema to rewrite their documents, so the reassurance mostly introduced the doubt it was trying to settle.
A document with no
title:in its frontmatter now takes its title from the first# headingin the body, falling back to the path only when there is no heading either. Agent-facing listings (exec cat,exec ls, the "most recent" line on a directory) were showing raw paths for files that plainly announce their own title on line one, which is the normal shape for vaults, imported notes, and simple logs./api/pagesand workspace search already resolved titles this way, so this brings the enrichment surface in line with the rest of the app, down to the details: atitle:is trimmed, and a blank or whitespace-only one now counts as no title and falls through to the heading rather than showing as an empty label.Diagnostic bundles now cover a useful window of time on projects with many linked files. The background sweep that repairs dropped file-watcher events re-checks every referenced file target every few seconds, and it was recording a telemetry span for each one even when nothing had changed. On a large project that is thousands of empty spans a minute, enough to fill the fixed-size local span buffer and recycle it while the user was still reproducing the problem, so a bug report often arrived carrying telemetry that no longer reached back to the failure being reported. Existence checks that find nothing to repair no longer record anything, so an idle project produces no index-update spans at all.
A component the editor does not recognize swaps itself for a raw-source view as soon as you open the document, and two things were reading that swap as though you had typed it.
The block wrapping such a component now keeps the source you wrote. When a component nested inside another one swapped first, the outer block was marked edited, lost its verbatim source, and was rebuilt from scratch with one newline at its opening and closing tags where you had written a blank line. The server wrote that second spelling over yours, which produces the same duplication and truncation described in the entry about typing in source mode, reached a different way. This covers the case where the wrapping block is itself unrecognized. It does not cover the other shape: when the wrapper IS one the editor recognizes, such as a callout, an accordion or a set of tabs, and only the component inside it is unrecognized, the server still rebuilds that wrapper and the duplication above can still happen to it. That case is tracked separately.
Opening any such document in a preview tab no longer makes the tab permanent. A single click on a file you only wanted to read was treated as a commitment, so the tab stopped being reusable and stayed in the strip.
Typing in source mode no longer re-indents, duplicates, or truncates a
<Steps>block you never touched. When one nested block inside a JSX container stopped parsing, the editor re-emitted that block's raw bytes indented two spaces per level while its siblings stayed flush-left, and the server then wrote that mixed spelling over your authored source. Landing while a keystroke was still in flight, the two spellings of the same block merged into each other: a<Step>block appeared twice, a line lost its tail, and the document stopped parsing. The raw bytes of a block the parser could not read are now emitted exactly as authored, so there is no second spelling to merge against.Terminal → New Terminal Window now inherits the project even when the window it was invoked from is still loading. The menu bar is live from the moment a window opens, but the app only recorded which project a window belonged to once that window's editor had finished loading. Invoking the command inside that gap resolved no project at all, so the new terminal opened silently in your home directory with no connection to the project you were looking at. The same gap also affected single-file windows and windows attached to an already-running server. A window is now associated with its project from the moment it is created, so the command resolves the same project throughout.
Typing in a large document no longer costs the server twice the markdown parsing it needs. Every keystroke in the visual editor makes the server re-derive the document's source text, and that work included parsing the document's current bytes from scratch even though the previous keystroke had just produced those exact bytes and parsed them already. On a 231 KB document that redundant parse was roughly half the per-keystroke cost. The server now reuses the parse it already has when the bytes are unchanged, and falls back to parsing whenever they are not, so documents whose source is not what the serializer would emit behave exactly as before. Typing on large documents should feel more responsive; there is no change to what gets written to disk.
Keep the suggestion pickers inside the editor pane
The slash, wiki-link and tag pickers are fixed-width — the slash menu is two columns totalling about 490px — so a pane narrowed by a docked terminal or agents rail could not hold them and they painted over the dock, the same escape the formatting bar and comment composer were just fixed for. They now cap their width to the pane, and the slash menu drops its preview column when the pane cannot hold both rather than squeezing each into something unreadable.
Setting up OpenKnowledge in an existing folder now asks about AI tools the same way creating a new project does: one checkbox covering the tools found on your machine, with a "What changes?" list of the exact files it writes. It replaces a list of eleven per-tool checkboxes buried in Advanced settings, and it fixes what that list promised. Five of those tools — Claude Desktop, OpenClaw, Antigravity, LM Studio and Hermes — only ever store settings for your whole account, never per project, so ticking them here wrote nothing at all; GitHub Copilot was listed the same way before it had been connected, when its project skill cannot be installed yet. Accepting the defaults also created config files for tools you don't have installed, scattering
.codex/,.opencode/and.pi/folders into repositories that never asked for them.The row now sits in plain view rather than behind Advanced settings — whether your project is reachable from your agents at all is not an advanced setting. Turning off individual tools still lives in Settings, under This project, where each row says what it writes and can be switched off again later.
Keyboard shortcuts no longer fire underneath an open dialog, command palette, or menu. Pressing ⌘T with the ⌘K palette up opened a new editor tab behind it, and the same leak affected ⇧⌘T, ⌃Tab, ⌘1–⌘9, ⌘L, ⇧⌘J, ⌘F, ⌘G, ⌘Z and ⌘⇧I — these are registered on the window, most of them in capture phase, so an overlay had no way to stop them from underneath. Every app-global shortcut now declines while a layer that owns the keyboard is open. Escape likewise dismisses only the topmost layer instead of also collapsing the graph panel or closing a diff pane behind it, and ⌘K no longer stacks the palette on top of another dialog while still dismissing the palette when it is the top layer. Editing keys the app never claimed — copy, paste, cut, select-all, undo, and arrow/Enter navigation inside the overlay — are untouched, and a hover-opened link panel no longer counts as owning the keyboard, so shortcuts keep working while the pointer rests on a link.
On the desktop app, ⌘N, ⌘,, ⌥⌘S, ⌥⌘B and ⌘J arrive as native menu accelerators rather than as renderer key presses, so they can still fire while an overlay is open; those five are fixed in the browser only.
Bug reports filed from an installed desktop app now say how the background server died. The app has recorded that for a while, but only in development builds: installed builds start the server as an ordinary background process rather than as one of the app's own child processes, and the code that wrote the record was wired only to the second kind. Every bug report from an installed build was therefore missing the one file that answers "did the server crash, or was it shut down on purpose", which is the question the record exists to settle. It is now written on both paths.
The record also names the signal that ended the process. A server killed outright leaves no exit code at all, so until now the death this feature was built for was the one it could not describe: the record looked the same as an exit for no stated reason. Records written by older versions simply have no signal in them, which reads as unknown rather than as "none".
Two honest limits worth stating. Where the app can classify a death itself it still does, but an installed build cannot classify this kind of process, so that field is now recorded as empty rather than filled in with a classification belonging to some other part of the app. The record now says which of the two it was, so an empty classification can be read as "there was never one to have" instead of "we looked and found nothing". And the record only covers a server this session started; when the app reconnects to a server that was already running from an earlier session, there is nothing watching it, so its death still leaves no record.
Nothing new is collected about you or your documents. The record holds a timestamp, the process id, the exit code, the signal that ended it, and which part of the app was watching. It stays on your machine unless you send it. Running
ok diagnose bundleyourself includes it, and so does a bug report filed with detailed diagnostics turned on; an ordinary bug report does not carry it yet.Fixed a crash that could take the whole editor down to the "Something went wrong" screen during navigation — most visibly right after creating a new file, and also when a document was renamed, deleted, or moved out from under an open tab. The editor tracked its open-document pool with a value that was rebuilt from scratch on every pool change, including changes that altered nothing you could see, such as re-opening a document that was already open. Each rebuild re-rendered the app shell and handed the navigation layer a fresh set of callbacks, which prompted it to re-open the document again. When a document's identity changed underneath that cycle, it fed itself until React aborted the render with "Maximum update depth exceeded". The pool value is now reused when nothing observable has changed, so the cycle can no longer close.
Agent tabs are easier to tell apart. Three changes to the tab strip:
- Unread indicator. A settled
readytab whose transcript advanced while you were on a different tab now pulses its status dot until you click into it — no more "did that other agent finish yet?" polling. - Per-thread identity. Every thread gets a stable, hashed tint ring around its agent icon, so a row of Claude tabs isn't just "same icon, truncated title" — each thread has a persistent visual anchor.
- Better auto-titles. The tab title now strips a leading agent-name address (
Claude, what's 2+2?→What's 2+2?, butCodex is failing to startpasses through unchanged), plus punctuation the prompt itself opens with (— refactor the parser→Refactor the parser), so the distinguishing words land at the start of the label.
- Unread indicator. A settled
ACP permission prompts with three or more options now stack vertically as full-width buttons — primary Allow up top, escalating grants and refusals below — instead of hiding the extras behind an overflow chevron. Prompts with one or two options keep the classic Deny-left / Allow-right row. Fixes the case where a 2-allow / 0-reject shape would silently drop its escalating grant, and always renders a refusal control (even when the agent offers none).
Plan checklists from ACP agents now carry an Approve / Ask changes / Reject row when the agent is waiting on you. Approve and Reject send a message on your behalf; Ask changes prefills the composer with "In the plan above, please …" so you can say what to adjust — no more typing "yes" as prose.
Comment highlights are blue instead of yellow. Yellow is what lint and audit problems use, and what the
==highlight==mark already wears, so a commented passage was about to be indistinguishable from a warning sitting on the same words. Comments now hold a colour of their own — the passage wash and its underline, the marker in the margin rail, the tint on the card being read, and the highlight on a commented property value all moved together, so the mark still reads as one thing wherever it appears.Skills served by a plugin the repo itself declares (a
.claude-plugin/marketplace.jsonwith in-tree plugin sources) now group under that plugin in every checkout of the repo. Grouping previously depended on the editor's plugin registry, which records installs per absolute path, so a second clone of the same repo showed those skills as a flat, ungrouped list. The repo manifest is now read directly, so the identity no longer depends on where the clone lives or whether the editor has registered it.Fix bold, italic, and other formatting being dropped from wiki links, images, inline math, tags, and line breaks.
Formatting applied to an inline element was lost as soon as the document round-tripped through the collaboration layer. Writing
**[[Page]]**in source mode, in the editor, or through the agent API produced[[Page]]on disk, and the emphasis could not be re-applied because it was discarded again on the next sync. A soft line break inside bold text split it into two separate bold runs.Before After **[[Page]]**→[[Page]]**[[Page]]**→**[[Page]]****→**→****a<br>b**→**a**<br>**b****a<br>b**→**a<br>b**This affects
[[wiki links]], images, image references, footnote references, inline math, tags, inline JSX, and hard line breaks.Documents already saved without their formatting are not repaired automatically — the formatting has to be applied again. If you run multiple clients against one server, update them together: an older client editing a document alongside an updated one will strip the restored formatting.
Clicking a file in the Files or Skills sidebar reuses one tab, the way an editor preview tab works. That is still the default, but it is now a setting: turn off Preview tabs in Settings (or set
editor.previewTabs: false) and every sidebar click opens in its own tab instead of replacing the current one. Pinned tabs are never reused either way, and back/forward navigation still reuses the active tab.Opening two projects at once no longer wedges one of them behind a red "the project's server is on a different branch than this window" banner. Each window remembers the git branch it last saw so that a freshly restored window can prove to the server which branch its cached content belongs to. That memory was stored under a single key shared by every open project, because it was scoped to the app rather than to the project. Whichever window wrote last decided what the next window read, so a window could start up claiming a completely different project's branch. The server correctly rejected the mismatched claim, the document then failed to load for 30 seconds, and the banner blamed the server even though the server was on the right branch the whole time. This was most visible right after an update, when every window restarts and reads that memory at once. Each project now keeps its own copy, so one project can no longer answer for another. The per-document lineage records, which had the same app-wide scope, are now per project as well.
Blank lines you add at the very top or the very bottom of a document now survive. Press Enter twice or more at either end and the blank lines reach the file on disk and every other open window, instead of disappearing the next time anything else touches the document. The threshold is two: a single blank line at either end is still not preserved, because one blank line there cannot be told apart from spacing nothing typed. At the bottom that is the empty line the editor keeps below the last block so you can carry on typing, and at the top it is the blank line that separates frontmatter from the body. The threshold is the same for every document, including ones where neither of those is present.
Remove the persistent “Reading from…” workspace-path label above the in-app agent composer. The label looked like live activity even though it was only a static workspace hint. Agents that receive file references instead of embedded contents now disclose that behavior contextually in the attachment-button tooltip.
Renaming an open skill no longer blanks the editor. The tab now follows the rename (sidebar row, address, and open document all update together), read-only plugin and built-in rows can be swept into shift-click and shift-arrow multi-selections without breaking the range walk, and a freshly created or imported skill opens on the first click instead of appearing dead until another skill is clicked. The Delete key (Cmd+Backspace on macOS) now bulk-deletes a multi-selection of skills with a live "Deleting N of M" progress label. Installing from skills.sh reliably lands on the new skill's tab: the sidebar no longer refetches every skill's bundle files after each change (their paths now ride the skills list), and the post-install redirect waits out destination moves instead of opening a document that is about to relocate.
Fixed a false "your update didn't install" notice that could appear while the update was in fact still installing. A beta-to-beta bump within the same version number, committed by a quit that no live process observed, lost the only timestamp the app had for when the install began, so the next launch judged a healthy install as failed. The app now keeps that timestamp across its own boot-time bookkeeping and holds the verdict until the install has really had its chance.
Opening a markdown file that a skill ships beside its SKILL.md no longer throws you out of the Skills sidebar. Many skills carry companion docs at the root of their bundle rather than under
references/, and clicking one right after installing switched the sidebar to Files, which read as the skill refusing to open. Every file inside a skill bundle now stays on the Skills surface.The bundled agent skills now match the MCP tool surface.
linksis called with the parameter names the tool accepts (document,sourceDocuments),auditis named as the end-state check and described by what it actually reports (every lint violation plus every broken internal link, not just dead links), withlinks({ kind: "dead" })as the graph reader and a superset ofauditon the source side, external images route through the ingest procedure instead of an ad-hoc fetch, and the OKF reserved-file frontmatter exemption is documented. The project skill refreshes on its own. A pack skill already seeded into a project is yours to edit, so it keeps its old text and seeding again will not refresh it; update that skill from source, or delete it and reseed, to pick up the corrected wording.Moving a skill between project and global no longer drops you into the file tree. When the only tab open for that skill was one of its bundle files, the move closed that tab, left the Skills sidebar with nothing to show, and fell back to Files on an unrelated document. The open tab now follows the skill to its new scope, and companion files a bundle ships outside
references/are recognized as part of the skill, so a move or a delete no longer strands their tab on a document that is gone.The skills list no longer gets stuck showing a skill's pre-change state after you install, remove, convert or move it. Every surface that shows skills shares one request to the server, and a refresh triggered by your change could latch onto a request that had already been sent before it — so the list settled on what was true a moment earlier and stayed there until something unrelated refreshed it. Refreshes now recognise a request as predating the change and issue their own instead.
Reopening a window with side by side editor panes no longer leaves the tab strips sitting off to one side of the panes they label. Panes have a minimum width, so a restored layout that asks for narrower panes than that gets them widened back to it, with the space reclaimed from whichever panes have room to give it up. The tab strips above kept the saved proportions instead, so with four panes open a strip could sit most of a tab's width away from its own pane. They now follow the widths the panes end up at, so they line up as soon as the window opens rather than only after the next thing that resizes the panes.
Opening a document in a window that has been idle no longer stalls on a blank editor for 30 seconds. If the window's connection to the server had dropped while it sat unused, the editor kept handing the document its dead connection and waited out the full sync timeout before recovering. It now reconnects the document up front, so it loads in the usual few hundred milliseconds.
A project can no longer get permanently stuck behind a stale server lock. When a project's
server.locknamed a process that was still running but no longer serving — which happens when a second server takes over the lock and the first one is later stopped, for example by an app update relaunch — the desktop app would connect every new window to that dead address. Every document in the project then showed "Connection dropped" and stayed that way: relaunching the app reproduced it exactly, and the in-app "Restart server" button gave up with advice to restart your computer, which could never have helped. One reporter's project was unusable for three days.When the app starts a server for a project and finds the lock held by some other process, it now checks that the other server is actually reachable before handing it to the window, instead of assuming it is ready. This is the same check the app already made when attaching to a server it did not start. If the check fails, opening the project now reports that a stopped server is holding it and offers to stop that process and retry, rather than opening onto a dead connection.
Clearing such a holder also works from both places it can be met. Previously only the "Restart server" button on an already-open window could recover, which was no help for a project too wedged to open in the first place; the "Stop Server & Retry" button on the failed-open dialog now applies the same rule. In both cases, a process that cannot be stopped but is verifiably not serving has its stale lock cleared so a fresh server can start, and a server that genuinely is serving is still left alone.
Scrolling back through terminal history no longer dead-ends after the panel changes size. Moving the terminal between its bottom dock and the right column while scrolled up left the view unable to go any further up, by wheel or by keyboard, and it stayed that way, because the resize left the scrollbar and the rows pointing at different lines. The lines above were never lost, but the only way back to them was scrolling all the way down and starting again. The panel now puts the two back in agreement whenever the grid resizes under a scrolled-back view.
Resizing the terminal still moves your place in the history: it keeps the most recent visible line on screen, so a tall panel becoming a short one leaves you further down the buffer than you were. That part is unchanged.
One thing is new, and it is the trade this makes: if the terminal resizes while a scroll of yours is still gliding to a stop, that glide now stops where it is instead of finishing. Reaching the history again is worth more than the last few lines of momentum, but it is a change you can notice.
Keep the formatting bar and comment composer inside the editor pane when a session dock narrows it
Docking the terminal or the agents rail beside a dragged-in editor column left the pane narrower than the formatting bar, and the bar painted its tail controls on top of the dock. Boundary-aware positioning could not fix it on its own: a clamp relocates a surface but cannot shrink one, so a fixed-width bar overhangs a narrower pane from every coordinate. Selection-anchored surfaces now cap their width to the pane, and the bar wraps to a second row rather than overflowing.
Pasting a copy of an entire document no longer makes the paste disappear. Selecting all, copying, and pasting produces a document that is an exact doubling of what was just saved, which is the same shape a stale browser cache produces when it merges an old copy back in. The persistence guard that watches for that corruption could not tell the two apart, so about two seconds after the paste it refused to save and rolled the open document back to the copy on disk. The pasted content vanished from under the cursor, and undo could not bring it back because the rollback arrives as a remote change that neither the server nor the editor tracks as undoable.
The guard now distinguishes the two cases by when the doubling appears. A stale-cache merge shows up as soon as the document syncs, before the session has saved anything; a paste is an edit on a document that has already saved cleanly. Pastes are left alone and save normally. When the guard does still act on a genuine corruption, it now writes a recovery checkpoint of the pre-rollback document first, so that version stays restorable from version history instead of being lost.
Make the Report a bug dialog wider so report details, diagnostic options, and screenshot previews are easier to review.
Restarting a project's server no longer leaves the window it was started from without a project. While the old server was terminated and a replacement window came up, the window still on screen belonged to no project as far as the app was concerned, so anything needing one came up empty for those few seconds: New Terminal Window opened without the project, popping a note out was refused, opening another terminal tab in that window was refused, and the window's session state stopped being saved. Those actions now resolve the project for the whole restart.
An update you downloaded is no longer thrown away when Windows ends your session, and the app no longer tells you an install failed when it never started one.
On Windows there were only two moments an update could actually install: you clicked "Relaunch now", or you quit the app and it installed on the way out. Shutting the machine down is neither. Windows terminates the app without giving it the chance to run its quit steps, so the update was silently discarded — and because the app had already recorded that an install was underway, the next launch reported a failure for something that had never been attempted.
For anyone who leaves the app open and shuts the laptop down rather than quitting, that repeated every time, so they never got an update at all. After three launches the failure notice gave up and went quiet too, leaving no sign that an update was sitting there ready to install.
The app now tells the two situations apart. An install that was genuinely handed over and did not take is still reported as a failure, unchanged. An update that no quit ever committed is recognized as still waiting, and is offered again the ordinary way — the same "ready to install" prompt you would have seen after the download — for as long as it is waiting. Nothing installs without you choosing it.