Repository navigation
Releases: oatnil-top/udctl
Release list
v0.160.4
v0.160.4 (2026-10-07)
Patch release: a backup no longer stops a busy SQLite server from saving, and a self-hosted server whose license has expired now starts in a recovery mode instead of refusing to start. No new database migrations. Self-hosted on SQLite and running v0.160.3: upgrade (see Upgrade Notes).
New Features
- Recover a self-hosted server with an expired license from the browser. Before, a server started with an expired license refused to start at all. Now, when the license is genuine but expired, the server starts in a restricted recovery mode: opening the app shows a recovery page naming the license and when it expired, and an admin can paste a new license there. The server returns to normal without restarting the process, and keeps the new license for later starts. No data is touched. A license that is forged or was issued for another server still refuses to start, as before.
- Start an agent session from a project. The project page and each row of the project list have a Start Session action. It opens the usual run dialog with the project already chosen, so the session runs in the project's directory, and on Run it creates a card for the session linked to the project. Choosing an existing card instead still works.
- Agent CLI rows that differ from the system default are marked in the list. In Your agent CLIs, a row whose command has drifted from the default of the same name carries a "Differs from default" tag. Clicking the tag opens the comparison with one-click overwrite, and the tag goes away as soon as the row matches the default again.
Bug Fixes
- A backup no longer leaves a SQLite server unable to save. On v0.160.3, a backup taken while the server was writing could make a save fail in a way that blocked every later save until the server was restarted. Backups now take their snapshot on the server's own database connection, and a save that fails under contention now fails on its own instead of blocking the ones after it. While the snapshot is copied, other database work waits for it.
- Admins are no longer held to the 1 GB storage limit on image uploads. Quick capture, creating a task from an image, the to-do vision upload and creating an expense from a photo checked an admin's storage quota as if they were a regular 1 GB user, and failed with "quota exceeded" past 1 GB.
- Replies routed into an OpenCode session arrive once. The terminal relay did not recognise OpenCode's "[Pasted ~N lines]" marker, so it pasted the same reply again and again and never pressed Enter. Fixed in both the ud CLI daemon and the desktop app, together with a related case where an earlier paste marker still on screen could be mistaken for the new one.
- Narrow screens: the "Differs from default" tag and the system-default comparison dialog no longer spill outside their box.
Upgrade Notes (self-hosted)
- No new database migrations.
- If you are on SQLite and running v0.160.3, upgrade. A backup on v0.160.3 can stop the server saving until it is restarted. If your server has stopped saving after a backup, restart it, then upgrade.
- A backup still needs temporary free disk space up to the size of the database, in the server's backup directory, on top of the archive itself, as in v0.160.3.
- A license pasted on the recovery page is saved as
license.tokenin the server's data directory, and on later starts it is used ahead ofLICENSE_TOKENand the config file for as long as it stays valid. If you later switch licenses throughLICENSE_TOKEN, delete that file, or it keeps being used. - If you pass the license with the
--license-tokenstartup flag, the recovery page cannot help you: that flag wins on every start, so the page refuses the new license and tells you to change the startup command instead. - Pro and Max: the recovery page refuses a license while
ADMIN_EMAILis not set, and names the missing setting, because the server could not finish starting without it. - The recovery page appears only when the server starts with an expired license. A server that is already running when its license expires keeps running.
The CLI is yours to upgrade: publishing a release does not change the ud on anybody's machine. There are three routes; use exactly one.
npm i -g @oatnil/ud # installed via npm
brew update && brew upgrade ud # installed via HomebrewIf your ud came from the desktop app, neither line above is your route: install the new desktop app. The app's "Install ud CLI" makes /usr/local/bin/ud a symlink into the app bundle, so the ud you run is the one the app ships.
Then confirm it took: ud --version must print udctl version 0.160.4.
v0.160.3
v0.160.3 (2026-10-06)
Patch release: backups are now a consistent copy of the database, and the OpenCode and GitHub Copilot agent CLIs start correctly. Before, a backup taken while the server was writing could be a damaged database that does not restore. No new database migrations. Self-hosted on SQLite: after upgrading, take a fresh backup (see Upgrade Notes).
Bug Fixes
- Backups are a consistent snapshot of the database. On servers that use SQLite, the backup copied the database file while the app kept writing to it, so a backup taken during writes could mix data from before and after a change and fail to restore. Each backup now first takes a consistent snapshot of the database and archives that snapshot. Backups already stored are not changed.
- One failed backup no longer makes the next night's backup report "failed" too. The scheduled backup job judges its health from the previous backup's result, so a single failed night made the following run report "failed" even when its own backup succeeded. A single isolated failure is now logged as a warning; two failures in a row, or no finished backup within the expected window, still mark the run failed.
- OpenCode agent sessions start. OpenCode took the session's startup line as a project directory and exited right after launch. Its default launch command is now
opencode --auto --prompt {{prompt}}. - GitHub Copilot agent sessions receive their startup line. Copilot has no plain positional prompt, so the startup line never arrived. Its default launch command is now
copilot --yolo -i {{prompt}}, which starts an interactive session with that line as its first message. - aider and kimi are no longer offered as default agent CLIs for new accounts. Neither can take a startup line and stay open, which agent sessions need.
Upgrade Notes (self-hosted)
- No new database migrations.
- Take a fresh backup after upgrading (Admin -> Backup), then check that a new backup file is listed there. A backup made by an earlier version while the server was busy may not restore; this is the first version whose backups are consistent.
- A backup now needs temporary free disk space up to the size of the database, in the server's backup directory, on top of the archive itself. The snapshot is deleted when the backup finishes. On a nearly full disk the backup fails and the job reports it.
- Your existing agent CLI settings are not changed. If OpenCode or GitHub Copilot is already in your agent CLI list (Your agent CLIs), it keeps its old command and still fails to start. Its row shows "Differs from the system default (last changed in v0.160.3)"; open "View differences" and change the command to the new default above. aider and kimi rows you already have stay as they are.
The CLI is yours to upgrade: publishing a release does not change the ud on anybody's machine. There are three routes; use exactly one.
npm i -g @oatnil/ud # installed via npm
brew update && brew upgrade ud # installed via HomebrewIf your ud came from the desktop app, neither line above is your route: install the new desktop app. The app's "Install ud CLI" makes /usr/local/bin/ud a symlink into the app bundle, so the ud you run is the one the app ships.
Then confirm it took: ud --version must print udctl version 0.160.3.
v0.160.2
v0.160.2 (2026-10-05)
Patch release: the nightly backup uploads again, and agent sessions now receive their instructions as a typed message. Since 0.160.0 a scheduled backup larger than the per-file upload limit was refused, so no backup file was stored. No new database migrations. Before you upgrade the server, read "Upgrade Notes": a machine whose ud is older than 0.160.2 cannot start agent sessions against a 0.160.2 server.
Bug Fixes
- Scheduled backups are stored again. On 0.160.0 and 0.160.1 the server checked the backup file against the per-file upload limit and the per-user storage quota in Admin settings, so any backup larger than that limit (10 MB unless an admin changed it) failed with
File size ... exceeds limitwhile the scheduled job still reported success. Backups now count only against the server-wide storage limit, as they did before 0.160.0. Normal uploads keep every limit they had. - An agent session that never gets going now says so. A session that hits the usage limit, exits right after launch, or does not read its instructions within 90 seconds is marked "failed" and replies in the thread that started it with the reason. Before, it stayed "running" and was later swept to "lost" with no explanation. When the usage limit was the cause, the reply quotes the limit message, including the reset time. A session now counts as having received its instructions only after the desktop app or the
udCLI daemon confirms it typed and submitted them; if a permission dialog swallowed them, they are sent again after 60 seconds. - A message sent to an agent that is waiting on a permission prompt no longer answers that prompt. Before, if the agent's terminal showed a permission dialog or a menu when a message arrived, the Enter that should have sent the message could approve the dialog instead, and the message was lost. The desktop app and the
udCLI daemon now check for an open dialog before typing, and never press Enter on a message they could not see arrive in the input box.
Improvements
- Agent sessions start with one short command and receive their instructions as a normal message. The startup line is now
ud send-input session <id> --briefing. The instructions are then typed into the session's terminal as a user message, instead of being pasted in or printed as command output.ud describe session <id> -o promptstill prints them for a person who wants to read them. - Attachments: the attachments panel's labels and messages follow the app language. Some were always shown in Chinese before.
Upgrade Notes (self-hosted)
- No new database migrations. Upgrading the server brings backups back; that fix is entirely on the server.
- Every machine that runs agent sessions needs
ud0.160.2. A 0.160.2 server starts each session withud send-input session <id> --briefing. Audolder than 0.160.2 rejects that withError: unknown flag: --briefing, so no session on that machine starts: each one is marked "failed" after 90 seconds. Upgradeudon those machines by the route below that matches how it was installed. If that machine'sudcame from the desktop app, the only way is to install the 0.160.2 desktop app. - The daemon on those machines needs 0.160.2 too: the desktop app 0.160.2, or a
udCLI daemon restarted onud0.160.2. A 0.160.2 server waits for the daemon to confirm that it submitted a session's instructions, and an older daemon never sends that confirmation. Its sessions get their startup line a second time after 60 seconds and are marked "failed" after 90 seconds, even when they are working. - If you run the
udCLI daemon, restart it after upgradingud: the running daemon keeps the old code until it is restarted. Stop it with the newud: an olderud daemon stoprun against a 0.160.2 daemon reports success while the daemon keeps running. The daemon's local port 19514 now answers 403 to requests made through[::1]or a host name; use127.0.0.1orlocalhost. - Upgrading the desktop app or restarting the
udCLI daemon ends the agent sessions running on that machine, so pick a moment when none are mid-task. - After the next scheduled backup, check that a new backup file is listed under Admin -> Backup. A job marked "success" is not enough, because the upload runs after the job is marked complete.
- Usage-limit detection needs the desktop app 0.160.2 on the machine that runs the agent sessions. With an older app, such a session is still marked "failed", but with the general reason.
The CLI is yours to upgrade: publishing a release does not change the ud on anybody's machine. There are three routes; use exactly one.
npm i -g @oatnil/ud # installed via npm
brew update && brew upgrade ud # installed via HomebrewIf your ud came from the desktop app, neither line above is your route: install the new desktop app. The app's "Install ud CLI" makes /usr/local/bin/ud a symlink into the app bundle, so the ud you run is the one the app ships.
Then confirm it took: ud --version must print udctl version 0.160.2.
v0.160.1
v0.160.1 (2026-10-04)
Patch release for the machines that run agent sessions: replies to a running agent reach its terminal in full again. No server changes, no new database migrations. Details under "Upgrade Notes".
Improvements
- Calendar: the peek has an icon that opens the task in a new tab, and "Open full task" in the edit drawer opens a tab and leaves the drawer open.
Bug Fixes
- A reply to a running agent session arrives as the message itself again. Since 0.160.0 the agent received a one-line
ud describe session <id> -o input <n>pointer and had to run it before it could read the message. The desktop app and theudCLI daemon now paste the full message into the agent's terminal, as before 0.160.0. Sessions still start with the short line, and no straygois typed after a message.
Upgrade Notes (self-hosted)
- No new database migrations, and nothing changed on the server. A 0.160.1 server behaves like 0.160.0; upgrading it only keeps the versions aligned.
- The fix is on the machines that run agent sessions: upgrade the desktop app or the
udCLI daemon there. Restarting the desktop app or the daemon ends the agent sessions running on that machine, so pick a moment when none are mid-task. ud describe session <id> -o input <n>still works, for messages that were already delivered the 0.160.0 way.
The CLI is yours to upgrade: publishing a release does not change the ud on anybody's machine. There are three routes; use exactly one.
npm i -g @oatnil/ud # installed via npm
brew update && brew upgrade ud # installed via HomebrewIf your ud came from the desktop app, neither line above is your route: install the new desktop app. The app's "Install ud CLI" makes /usr/local/bin/ud a symlink into the app bundle, so the ud you run is the one the app ships.
Then confirm it took: ud --version must print udctl version 0.160.1.
v0.160.0
v0.160.0 (2026-10-04)
Self-hosted: upgrade the server first, then every machine that runs agent sessions (desktop app or ud CLI daemon, and the ud on its PATH) to 0.160.0 right after. Until a machine is upgraded, Claude Code and Codex sessions on it fail to start. No new database migrations. Details under "Upgrade Notes".
New Features
- Copy a Markdown link to a task from its title row. One click on the task detail page (wide and narrow layouts) and in the calendar peek.
- Agents act with the permissions of the person they work for. An agent working for an admin can now read and change system settings, the same as that admin; an agent working for anyone else is refused, as before. Every admin action an agent takes is logged with both the agent and the person it acted for.
Improvements
- Task details read as a ledger. Properties sit in five groups with one label column; dates and times use one format (
YYYY-MM-DD HH:mm, 24-hour); the sprint row shows the sprint's title; custom fields drop thecf.prefix. The board row shows the task's boards as chips you can click, add and remove. - Mobile task details list the properties inline, give attachments their own section, and rename "Details" to "Relationship", in the same order as desktop.
- Attachments are listed as plain rows with their upload time; thumbnails carry a filename bar. Uploaded files show the upload time, not only the date, in preview, inspector and the mobile list.
- Calendar peek: clicking the title opens the edit drawer; moving or removing a slot are icon buttons in the action row.
- Desktop app: links that open a task in a new tab now open a new tab inside the app.
- Agent sessions start one way, decided by the server. Every agent session is started as
<agent cli> <short line>; the server builds the whole command, including the board's tmux setting, and records exactly the command that runs. A{{prompt}}placeholder in an agent CLI command marks where the short line goes. - Messages to a running agent are no longer pasted into its terminal. The agent receives one line and reads the full message from disk, so long and multi-line messages arrive intact. No session gets a stray
gotyped after a message any more, including the first message of a human session. - A session whose agent never reads its brief gets a hint. The reply posted in the thread suggests changing the launch command to
<cli> <flag> {{prompt}}for CLIs that do not take the prompt as a plain argument. - The built-in Alfred agent's default principles are now written in English.
Bug Fixes
- Switching to another task by editing the address bar on a task detail page no longer sometimes shows "Something went wrong".
- The blank band at the top of the task detail page is gone.
ud.projectsandcf.release_trainno longer appear twice in a task's properties.- The sprint row is no longer blank when the sprint is on no board.
- Schedule times on the task detail page used a 12-hour clock; they are 24-hour like everywhere else.
- Section header and Relations icons are sized and spaced consistently.
- The "Open in new window" label on the board header is translated in Chinese.
- Admin-only backup routes (start, status, download) now check the caller's admin permission on the server as well, so a token carrying an outdated admin claim can no longer start or download a full database backup.
Upgrade Notes (self-hosted)
- No new database migrations.
- Upgrade the server first, then the machines that run agent sessions, with as little time between as you can. The server now puts the short line into the launch command itself. A daemon from 0.159.0 or older adds it a second time, and Claude Code and Codex sessions on that machine fail to start; upgrading the daemon fixes it. The other order is safer but not clean: a 0.160.0 daemon against an older server starts the agent without the short line, and the server re-sends it after 60 seconds.
- The
udon each of those machines'PATHmust be 0.160.0 as well. Messages to a running agent now arrive as one line asking it to runud describe session <id> -o input <n>, which olderudversions do not have. - Agent CLI extra arguments are limited to letters, digits, spaces and
._=:@/,+-, plus the{{prompt}}and{{args}}placeholders. Saving anything else is refused (AGENT_CLI_ARGS_INVALID). An existing setting that already contains other characters makes the session fail to start, with the reason posted in the thread; edit the arguments to fix it. - The board's tmux session setting is now applied by the server. On a Windows machine a tmux setting makes the session fail to start, with the reason posted in the thread, instead of being skipped silently. Clear the setting for boards whose sessions run on Windows.
- Agents now get the admin surface when the person they work for is an admin. Sessions you dispatch as an admin can read and change system settings, create users and API keys. These accesses are logged with the agent and the person. An agent account itself can no longer be given the admin role, and an API key owned by an agent never counts as admin, whatever role it was created with.
The CLI is yours to upgrade: publishing a release does not change the ud on anybody's machine. There are three routes; use exactly one.
npm i -g @oatnil/ud # installed via npm
brew update && brew upgrade ud # installed via HomebrewIf your ud came from the desktop app, neither line above is your route: install the new desktop app. The app's "Install ud CLI" makes /usr/local/bin/ud a symlink into the app bundle, so the ud you run is the one the app ships.
Then confirm it took: ud --version must print udctl version 0.160.0.
v0.159.0
v0.159.0 (2026-09-30)
Self-hosted: before upgrading the server, make sure every machine that runs agent sessions has ud 0.158.0 or later. From this release every agent session starts with a short line and reads its brief from disk; an older ud cannot read it. No new database migrations. Details under "Upgrade Notes".
Improvements
- Agent sessions always start the short way. The agent is handed one short line and reads its full brief from disk, instead of having the whole brief pasted into its terminal. This used to be the opt-in instance setting
workspace.promptDelivery=short-string; it is now the only behaviour, and the setting is gone. - Claude Code and Codex sessions no longer get a stray
goafter their first message. The short line is passed on the command line and submitted as it is. - A session that never reads its brief is reported, not silent. If the agent has not confirmed reading its brief within 60 seconds, the short line is sent again; after 120 seconds a reply is posted in the thread that started the session.
Upgrade Notes (self-hosted)
- No new database migrations.
- The
workspace.promptDeliverysetting is removed, and every agent session now uses the short line. On every machine that runs agent sessions, upgrade the desktop app or CLI daemon, and theudon itsPATH, to 0.158.0 or later before you upgrade the server. An olderudignores the flag the short line asks for, so the agent does not receive its instructions; the server notices the missing read receipt and, after 120 seconds, posts a reply in the thread that started the session. A value already stored forworkspace.promptDelivery(either value) stays in the database and is ignored; the upgrade does not fail because of it.
The CLI is yours to upgrade: publishing a release does not change the ud on anybody's machine. There are three routes; use exactly one.
npm i -g @oatnil/ud # installed via npm
brew update && brew upgrade ud # installed via HomebrewIf your ud came from the desktop app, neither line above is your route: install the new desktop app. The app's "Install ud CLI" makes /usr/local/bin/ud a symlink into the app bundle, so the ud you run is the one the app ships.
Then confirm it took: ud --version must print udctl version 0.159.0.
v0.158.0
v0.158.0 (2026-09-30)
Self-hosted: back up the database before upgrading. One migration runs on first boot and only adds a column. If your server is started with --license-private-key or LICENSE_PRIVATE_KEY, remove it first: the setting no longer exists, and an unknown command-line flag stops the server from starting. Details under "Upgrade Notes".
New Features
- Desktop: activate a Pro or Max license in the app. Settings has a new License section: paste a license to activate, renew or remove it. If the stored license has expired or does not verify, the app opens on a renewal screen instead of starting the backend.
- Desktop: optional public access address. If you expose the desktop backend through your own tunnel or domain, enter that address under Settings. Attachment links and cross-origin requests then use it, so a remote browser gets working file links instead of
localhostones. Leave it empty and nothing changes. The app does not run the tunnel for you. - Desktop: visitors over a tunnel get the full web UI served by the desktop backend itself.
Improvements
- Desktop: the backend always listens on the same port, 24816. It used to take the first free port from 8888 up, so a tunnel or direct address could silently stop working after a restart. If the port is taken, the app shows what is holding it and does not start on a different one.
- Desktop: when the backend fails to start, the window says why: the tail of the backend log, the process holding the port, and where the port setting came from, instead of "Check Console.app".
- Comment threads: "Annotate" is now "Keep" (Chinese: 留存), with a bookmark icon. Starred threads are listed first in the conversation list, and the conversation status badges are translated.
- Task status colours match the mobile app: in progress is blue, pending is amber.
- Session activity colours are consistent across the session tables.
Bug Fixes
- Images pasted into a comment no longer appear in the card's attachment list. Group members can still open them.
- A link to an uploaded file written in a comment without the leading
!shows as a file, not as raw markdown. ud get task --deletedworks again, andud describe task --deleted, restore and permanent delete accept a short ID.- Plain shell sessions started by the CLI daemon take input as typed. Commands like
pwdwere arriving mangled, and a live CLI-daemon session could be reported as exited right after it started.
Upgrade Notes (self-hosted)
- Back up your database before upgrading. First boot runs one migration,
00094_add_prompt_read_at_to_workspace_sessions, which adds a nullableprompt_read_atcolumn toworkspace_sessions. Nothing is removed. - Server-side license signing is removed.
POST /license/generate,GET /license/decode, the web pages/licenseand/license-pro, the--license-private-keyflag, theLICENSE_PRIVATE_KEYenvironment variable and the hiddenlicense.private_keysetting are gone. Remove--license-private-keyfrom your start command before upgrading: the server refuses unknown flags and will not start. A leftoverLICENSE_PRIVATE_KEYenvironment variable is ignored. License verification (LICENSE_TOKEN,/license/info) is unchanged. - Self-hosted Personal instances no longer auto-login in the browser. The Personal-tier password is no longer returned by the public
/auth/tier-infoendpoint — it was readable by anyone who could reach the instance, including over a tunnel. Browser sign-in is now manual: usepersonal@undercontrol.localwith the password from the server's startup log or itsPERSONAL_TIER_PASSWORDsetting. The desktop app still signs in with one click. - The desktop app now generates a unique Personal-tier password per install instead of the fixed
personal123. Connecting to the desktop backend from the CLI or another device usingpersonal123no longer works; view or reset this machine's password under Settings → Password. - Desktop: the backend port is now 24816. Anything that pointed at the old port (a tunnel, a CLI context, a bookmark) needs updating. To use another port, set
UD_BACKEND_PORTor put the number in a.port-overridefile in the app's data directory. - New instance setting
workspace.promptDelivery, defaultpaste-full, which is the existing behaviour.short-stringhands agent sessions a short line and has them read the full brief from disk. Turn it on only after every machine that runs a daemon has the 0.158.0 CLI and desktop app: an olderudignores the flag the short line asks for, and the agent never receives its instructions.
The CLI is yours to upgrade: publishing a release does not change the ud on anybody's machine. There are three routes; use exactly one.
npm i -g @oatnil/ud # installed via npm
brew update && brew upgrade ud # installed via HomebrewIf your ud came from the desktop app, neither line above is your route: install the new desktop app. The app's "Install ud CLI" makes /usr/local/bin/ud a symlink into the app bundle, so the ud you run is the one the app ships.
Then confirm it took: ud --version must print udctl version 0.158.0.
v0.157.0
v0.157.0 (2026-09-27)
Self-hosting? Upgrade the server before the apps and the CLI. This release's web app, desktop app and CLI call API paths that older servers do not answer, so a 0.157.0 client against an older server fails; older clients keep working against the new server. Back up your database first: two migrations run on first boot, and both only add. Details under Upgrade Notes below.
New Features
ud sprint start <id> --board <board>starts a sprint from the CLI in one action. It flips the sprint to in progress and points the board at it in a single write, so a failure leaves neither half done.--kickoff/--deadlineset the window in the same call, andud sprint list --board <board>reads it back.- Closing a sprint also moves the board on. Every board that pointed at the closed sprint is repointed at the rollover sprint (or cleared, when the rest goes to the backlog).
ud sprint closeprints which boards it moved, and names any board it skipped because you cannot write to it. ud move task <id> --board <board> --from <column> --to <column>moves a card between columns in one step, running the board's exit and enter actions together on the server.--dry-runshows what would change. If the board changed under you since you last read it, the move is refused instead of landing on stale data.- Reading a board from the CLI shows what the board shows.
ud get task --board <board> --column <column>,--sprint,ud describe board(with a count per column) andud sprint listread the same columns the app reads, including the active sprint's scope, and end with a footer saying whether the listing is complete. ud applywithboard:andcolumn:creates the card the way the column would: status, tags and the active sprint are set by the server from the column's rules.
Improvements
- Dragging a card to another column is smooth. The card lands in the target column immediately instead of bouncing back to where it came from and jumping over a moment later. A cross-column drop is one request now.
- Starting a sprint updates the board you are looking at straight away. The columns used to keep showing cards from outside the sprint until the page was reloaded.
- Creating a card inside a column lets the server apply that column's rules, and a sprint's start and end dates are saved in one write.
- A comment reply that fails to send stays on screen marked "Not sent", with Retry and Discard. It used to disappear, leaving only a toast that scrolls away.
- Desktop: Cmd+R / Ctrl+R reloads the focused window, as in a browser, and View > Reload does the same.
- The board's column list and the built-in All Tasks columns come from the server, so every client shows the same columns.
ud query boardwithout a query is one request for the whole board, instead of one per column.ud get task --status in_progress(a status that does not exist) is an error that lists the valid values. It used to print "No tasks found", which looks exactly like an empty result.
Bug Fixes
- Desktop: launching the app a second time no longer starts a second copy against the same data. The second launch used to delete the running app's single-instance lock and start anyway, so two copies shared one profile and one daemon.
- Agent sessions started by the desktop daemon no longer stall on arrival about one time in four. The daemon now types a short word after pasting the prompt, so the session always has something to act on.
- Self-hosted on SQLite: a project's task list no longer fails with "malformed JSON", and returns the tasks it should.
Upgrade Notes (self-hosted)
- Back up your database before upgrading. Two migrations run on first boot. Both only add:
00092_add_status_to_budgetsadds astatuscolumn tobudgets(every existing budget becomesactive), and00093_add_content_hash_to_resourcesadds a nullablecontent_hashcolumn and an index toresources. Nothing is dropped. - First boot also rewrites some boards, once. Board columns saved without an id get one, and their column-query actions are regenerated, so moving a card can address the column by id. This runs at startup, not as a migration; it is idempotent and later starts change nothing. The backup above covers it.
- Upgrade the server before the apps and the CLI. The API now also answers under
/api/v1/tasksand/api/v1/boards, and this release's web app, desktop app and CLI call those paths. The old paths (/api/v1/todolist,/api/v1/kanban/boards) keep working, and no endpoint was removed, so older clients are fine against the new server. The other direction is not: a 0.157.0 desktop app or CLI pointed at an older server fails. - Closing a sprint now changes boards too. Any board whose active sprint was the one being closed is repointed by the close itself. Scripts that did that repointing by hand can drop the step.
- No new configuration settings, and no settings removed.
CLI upgrades are yours to run: publishing a release does not change the ud on anybody's machine. There are three routes — take only one.
npm i -g @oatnil/ud # installed via npm
brew update && brew upgrade ud # installed via HomebrewIf your ud came from the desktop app, neither line above is your route — install the new desktop app instead. The app's "Install ud CLI" makes /usr/local/bin/ud a symlink into the app bundle, so the ud you run is the one the app ships.
Then confirm it took: ud --version must print udctl version 0.157.0.
v0.156.0
v0.156.0 (2026-09-21)
New Features
ud find— one command that answers "which command do I use to find X". It maps what you are looking for (a task, a note, a comment, a skill, an agent, a board) onto the command that finds it, and for each one it also names what that command does not return — so an empty result is no longer ambiguous between "there is none" and "you asked the wrong command".- A scheduled calendar block can be deleted, or moved to a different task, from the block itself. Click a block on the calendar: the peek now carries a delete button and a re-bind control, so re-planning no longer means deleting the block and building a new one.
- Deleting a folder now deletes what is inside it. It used to dissolve the folder and move its contents up one level. Now the server deletes the whole subtree, and before it does, a confirmation names how many items will go and warns that anything without a recycle bin — resources, diagrams — is deleted permanently. Cancel changes nothing. Tasks go to the recycle bin and can be restored.
- Desktop: every tab keeps its own page history, with back and forward buttons. Navigating inside a tab no longer costs you the way back.
- Desktop navigation is one level flatter. The Runtime group is gone; CLI, Workspace Prompts and Daemons are top-level tabs.
- The agent detail page was rebuilt around a single Edit switch. Sections read as plain text until you press Edit, which flips the whole page into edit mode at once, instead of each field carrying its own editor.
Improvements
- Agent principles are an ordinary agent field now. They are edited with everything else on the agent page,
ud applywrites them (on create as well as on update),ud describe agent -o applyround-trips them, and every change is recorded as anagent.updatedaudit event that is kept forever. ud applywrites every document in a multi-document file, not only the first. A file holding several documents used to write the first one and pour the rest into its body.ud applycan clear tags. An empty tag list now reaches the server, and the echo reports the cleared state.ud describe taskends by saying how many comment threads the task has, so "this task has no comments" and "I did not show you its comments" stop looking the same.- Writing metadata no longer echoes back a result nobody verified. A key that does not exist is an error at the call site instead of a success line, and a delete really deletes.
--set cf.points=...is refused by name and points you atud.points. - Table output truncates by display width. A column cut through the middle of a wide character used to make a grep over the whole output find nothing — which reads exactly like a miss.
--column todomatches a column named "To Do". The example in the command's own help no longer fails when you copy it.ud cookrecipes and the--helpexamples were checked by running them, each on a board the recipe itself names.- Expired rows in the account switcher say so, and prefill the login form. Switching to a session whose token has expired used to look like an ordinary switch.
- Calendar quick-create waits until you stop typing (3 seconds) before asking the backend, and searches the whole local cache in the meantime.
- Calendar block colors follow live task status. Change a status and the block repaints immediately, over a new set of attention colors.
- A screenshot pasted into a comment box is re-encoded before upload, so pasting one costs a fraction of what it did.
- Budget: adding a plan resolves overlaps with the plans already there, and period totals accrue per period.
- Desktop task detail: click the description heading to collapse the whole section.
- The contact page points at a Telegram group instead of Discord.
- The product reads as
udctlin the browser title, the login and onboarding screens, and About.
Bug Fixes
- Mermaid arrowheads no longer disappear when a page holds more than one diagram. Marker ids are scoped per diagram, so an arrowhead can no longer resolve against a hidden one.
- A folder delete that fails no longer reports success. The tree is put back from the pre-delete snapshot and the failure is shown; deleting explorer items reports which ids failed instead of throwing.
- Creating an agent keeps the principles you gave it. They were dropped silently on create.
- The agent pages no longer serve stale data, and a save that would overwrite somebody else's concurrent edit is stopped before it lands.
- Frontmatter unquoting strips the quotes, rather than trimming characters out of the content.
- Self-hosted:
/healthreally reads the database now, and the SQLite connection pool gained two guards.
Upgrade Notes (self-hosted)
- Back up your database before upgrading. Two migrations run on first boot and both of them drop something.
00090_drop_agent_cli_user_setclears machine-guessed CLI picks and then removes theagent_cli_user_setcolumn fromagent_configs.00091_drop_agent_principles_revisionsdrops theagent_principles_revisionstable with every row in it; the principles values themselves live on the agent and are not affected, but the change journal is gone. Rolling either one back restores the shape, not the data. - Two API endpoints were retired.
PUT /api/v1/agents/:id/principlesandGET /api/v1/agents/:id/principles-historyno longer exist; principles are written throughPUT /api/v1/agents/:id, and the CLI's--principles-historyflag went with them. Anything scripted against those two paths needs changing. - Deleting a folder changed meaning. It used to move the folder's contents up one level; it now deletes them. Resources and diagrams have no recycle bin, so for those it is permanent.
- No new configuration settings, and no settings removed.
CLI upgrades are yours to run: publishing a release does not change the ud on anybody's machine. There are three routes — take only one.
npm i -g @oatnil/ud # installed via npm
brew update && brew upgrade ud # installed via HomebrewIf your ud came from the desktop app, neither line above is your route — install the new desktop app instead. The app's "Install ud CLI" makes /usr/local/bin/ud a symlink into the app bundle, so the ud you run is the one the app ships.
Then confirm it took: ud --version must print udctl version 0.156.0.
v0.155.0
v0.155.0 (2026-09-15)
New Features
- Agents can carry principles — a short set of red lines re-injected into every prompt they receive. Each agent now has a
principlesfield, capped at 200 characters. Whatever you write there is appended to the end of every prompt that agent is sent, so it is re-read on every delivery rather than once at the top of a long session. Keep it to the one or two lines that matter most; the editor warns you as it gets long, because shorter principles are re-read more reliably. - Only the agent's owner can change them, and every change needs a reason. The principles editor lives on the agent's detail page in the web app. Saving a change requires a one-line change note, and the full history — who changed it, when, and why — is readable from the same dialog under Change history.
- The same field from the CLI.
ud describe agent <name>shows an agent's principles,ud describe agent <name> -o promptrenders them inside the prompt the agent actually receives, andud describe agent <name> --principles-historyprints the change journal. - A delivery says what happened to the principles. Every prompt delivery records whether the principles were injected, were empty, or could not be fetched. A failure to read them does not block the prompt — the agent still gets its message, and the failure is recorded rather than turned into an outage.
Improvements
ud explain agentmarks read-only fields as read-only. Fields the server owns and refuses to accept from a write —principlesamong them — now render as read-only inud explain, so a field you cannot set no longer looks like one you forgot to set.- A self-hosted instance whose object storage falls back now says so, loudly. When the configured storage class cannot be resolved, the server falls back to the storage built from its startup
--s3-*flags. Two of the three places this happens logged a warning that named the failure but not the consequence, and the third logged nothing at all. All three now logBLOB_FALLBACK_USEDat error level with the reason and a running count. This matters during a storage migration: serving from the old bucket looks exactly like serving from the new one, until the old bucket is deleted.
Upgrade Notes (self-hosted)
- Back up your database before upgrading. Two migrations run on first boot:
00088_add_principles_to_agent_configsand00089_create_agent_principles_revisions. - The built-in
alfredagent ships with seed principles, and a seed only reaches a newly created agent. An instance that already created its built-in agents will findalfred's principles empty after this upgrade — that is expected, not a failed migration. Set them from the agent's detail page if you want them. No other built-in agent carries seed principles. - No new configuration settings, and no settings removed.
CLI upgrades are yours to run: publishing a release does not change the ud on anybody's machine. There are three routes — take only one.
npm i -g @oatnil/ud # installed via npm
brew update && brew upgrade ud # installed via HomebrewIf your ud came from the desktop app, neither line above is your route — install the new desktop app instead. The app's "Install ud CLI" makes /usr/local/bin/ud a symlink into the app bundle, so the ud you run is the one the app ships.
Then confirm it took: ud --version must print udctl version 0.155.0.