Upgrade from 2.0 to 2.7
Back up, upgrade, verify, and adopt the changes from 2.1 through 2.7 on an existing Lific 2.0 installation.
This guide covers upgrades from Lific 2.0 through 2.7. It assumes an existing 2.0 instance and deliberately does not describe older migration paths.
Before upgrading
-
Record the selected configuration and database paths with
lific instance infoand your service status command. -
Make a complete backup while the service is stopped or quiesced:
lific dump --out ./lific-before-2.7.tar.gzThe archive includes a consistent database snapshot, attachments, and
manifest.json. -
Record how clients connect: browser URL, API-key users, OAuth clients, and MCP configuration files. Keep one known-good operator key available for recovery.
-
If the instance is behind a reverse proxy, review
server.trusted_proxiestwice: before starting 2.2, which introduced the setting with a default that trusted loopback peers, and again before 2.7, where the default changes to trust no proxies at all. From 2.7, a deployment whose proxy connects from loopback (Tailscale serve or Funnel, or nginx on the same host) must list that proxy explicitly, or every client shares the proxy's rate-limit bucket. Add only isolated proxy networks you operate. -
Confirm that
lific.tomlparses before rolling out 2.6. From 2.6 a config file that exists but cannot be read or parsed stops startup instead of falling back to defaults, and unknown keys are rejected. Runlific doctor, which reports the configuration as one check and continues through the rest.
Stop the background service before replacing the binary or restoring a backup. Keep the same lific.toml, database path, attachments directory, and public URL unless you are intentionally relocating the instance.
Changes to account for
v2.1
lific membermanages project membership and roles from the CLI.lific user set-passwordprovides an operator password reset and invalidates the user's sessions.--configis honored byinitand service installation, and configuration/database discovery follows standard OS directories.- Private instances may set
[auth] required = false; this is a shell-controlled operator setting and must remain local or firewalled.
v2.2
- Browser views use credentialed realtime invalidation. Sessions are revalidated, connections are capped per user, and reconnects trigger a resync.
server.trusted_proxiescontrols when forwarded client-IP headers are trusted for rate limiting.- MCP output is compact by default. Use the documented opt-in fields such as
include_closed,include_comments, orecho_treewhen an agent needs the full view. - MCP search supports literal mode, comments are searchable, and list/comment operations expose pagination hints.
- The web UI adds list sub-tabs, sidebar recents, touch page movement, PWA install metadata, and improved command-palette results.
- Auth-optional mode now reaches the browser through the instance auto-login flow; with no accounts, the signup screen still appears.
v2.2.1
- The three export MCP tools are now one
exporttool. It dispatches by identifier:PRO-42for an issue,PRO-DOC-3for a page, and barePROfor a project. - The MCP surface is 27 tools. Clients that assumed the older 29-tool count or the removed export tool names must refresh their discovery data.
v2.3
- The CLI can run data commands against a running server with
--backend http, using--urlorLIFIC_URLand--api-keyorLIFIC_API_KEY. The direct SQLite backend remains the default. - OAuth discovery works on an instance with no
server.public_url. An explicitpublic_urlis still authoritative.
v2.4
- One migration runs automatically on first launch. Project groups start empty for every user, so the sidebar renders as before until you create one.
v2.5
- No migrations. Identifiers render as Markdown links in MCP, CLI, and REST output;
server.public_urlpins the link base, and Lific otherwise derives it from an allowlisted request host.
v2.6
- Two migrations run automatically on first start. They add the per-tool identity plumbing behind connected agents, and there are no manual steps.
- A configuration file that exists but cannot be read or parsed is now a fatal startup error, and unknown keys are rejected. A missing file still starts on the built-in defaults. Check that
lific.tomlparses before the rollout;lific doctoris the tool for it. - A server with
web_auto_loginenabled refuses to start when[server] hostis not loopback. Bind127.0.0.1or turn auto-login off. - REST authentication failures are uniformly
403with the messageauthentication required. Endpoints for profile, password, sessions, API keys, connected tools, and comments previously answered400for this case. A client that reads400from those endpoints as "signed out" must key on403instead. - Remove
secure_cookiesfrom[auth]if your config sets it. It has never been read from the file, and unknown keys are now an error rather than being ignored. lific initpresents an auth-mode choice on a fresh install and creates the first administrator directly. An existing instance with users skips this entirely.- Connected AI tools have identities of their own. Audit entries for their writes are attributed to the tool rather than to the operator who connected it.
v2.7
Go to 2.7.1, not 2.7.0. In 2.7.0 every issue page in the web UI failed to render, on every instance. 2.7.1 fixes it and changes nothing else. The two releases are otherwise identical, so everything below applies to both.
The largest upgrade since 2.0. Three things need an operator decision before you roll it out: server.trusted_proxies stops defaulting to loopback, an older binary will refuse to open the database once 2.7 has touched it, and account recovery revokes much more than it used to. Take the pre-upgrade archive described above; on this release it is the only way back.
Account recovery changed shape. Read this part before rolling out if anything automated holds an API key.
-
API-key callers lose credential-minting permission.
POST /api/auth/keysandPOST /api/auth/botsnow require a browser session token created within the last 15 minutes and answer403 recent authentication requiredto anything else, API keys included. This is deliberate: a key that could mint another key outlived the revocation of the key it came from. A script that provisioned keys by presenting an existing key must move tolific key createon the server, which writes to the database directly.lific connectis unaffected for the same reason. -
A password change or sign-out-everywhere now revokes far more than sessions. For the account and every connected-tool bot it owns: sessions, API keys, OAuth access tokens, unexchanged authorization codes, and uncollected device approvals.
lific user set-passworddoes the same. Plan for reconnecting tools and reissuing any API key a script depends on after a reset. Unbound operator keys are not affected. -
A password change returns a replacement session in
token, and it is the only credential left that works. A REST client that changes a password on a user's behalf must store that token before its next request. The web UI does this for you. -
A running stdio MCP agent stops at its next tool call once its key is revoked.
LIFIC_TOKENis revalidated on every tool call rather than only at startup. Expect agents to needlific connectplus a client restart after any recovery. -
OAuth access tokens can no longer approve an authorization request or a device code, and the browser session that approves must have signed in within the last 15 minutes. Approve from a signed-in browser, signing in again first if prompted. A tool that scripted its own re-approval through a token it already held will stop working.
-
Every access-expanding admin endpoint requires a recent browser session.
POST /api/users,POST /api/users/{id}/promote,POST /api/users/{id}/reactivate,PATCH /api/instance/settings,POST /api/projects/{id}/members, role increases throughPATCH /api/projects/{id}/members/{user_id},PUT /api/projects/{id}when it setslead_user_id(which grants a lead membership), andPOST /api/projectswhen it names a lead other than the caller. Creating a project with no lead, or with yourself as lead, is unchanged, so automation that creates its own projects keeps working. Each answers403 recent authentication requiredto an API key or OAuth token, because each leaves access behind that a lockdown aimed at the calling credential cannot reach. Scripted user provisioning moves tolific user createandlific user promoteon the server. Reductions are untouched:demote,deactivate, role downgrades and member removal all still work from an API key, so containment stays fast. -
Instance settings are gated as a whole, not field by field. A patch that only changes
instance_nameis refused from an API key just like one that turns onweb_auto_login. Classifying each field as expanding or not is a rule that has to be re-derived every time a field is added, and one miss reopens the hole. -
An OAuth grant that is not bound to an identity can no longer be exchanged. Authorization codes and device approvals stored before 2.1 have no user attached; exchanging one produced an access token that named nobody and was therefore treated as the operator, and which no account recovery could revoke. Both exchange paths now return
invalid_grantfor these. Codes expire in ten minutes and device codes in fifteen, so in practice this only affects a client mid-flow across the upgrade: have it authorize again. Access tokens already issued are unchanged; revoke any unbound one you find withDELETE FROM oauth_tokens WHERE user_id IS NULLand reconnect the tool. Rolling back is now a restore, not a binary swap. -
Five migrations run automatically on first start: bot-identity uniqueness (which merges any duplicate agent identities into the oldest, keeping memberships, groups, and saved views), case-insensitive project identifiers, the account active flag, attachment dimensions and alt text, and the attachment search index. The
_migrationstable also gains a checksum column, backfilled from the migrations this binary carries. No manual steps. -
An older Lific will refuse to open a 2.7 database. Migrations only run forwards, so an older binary used to find the version already stamped, apply nothing, and serve a schema it was never compiled against. Startup now fails with an error naming the database's schema version and the highest the binary supports. To go back, restore the pre-upgrade archive.
-
A hand-edited migration file is now a startup failure. Applied migrations are checksummed, and a stored hash that no longer matches names the migration and both digests. If you have ever patched a migration in place, restore the original before upgrading and put the correction in a new migration.
-
[backup] retain = 0andinterval_minutes = 0no longer do what they said. Zero retention deleted every archive seconds after writing it, and a zero interval panicked the backup task on its first tick, silently ending backups while the server kept serving. Both now warn and fall back to the defaults, 24 archives every 60 minutes. Setenabled = falseto turn backups off.
Bounded responses. Every surface that could return an unbounded result is now paged or capped. A client that relied on getting everything in one call needs to page.
- Comment lists default to 50 and cap at 500.
GET /api/issues/{id}/commentskeeps itsorder=ascdefault, so an unqualified read is now the oldest 50; page withoffset, or passorder=desc. The MCPlist_commentstool andlific comment listdefault to the 50 newest instead, so a script that expectedlific comment listto print a whole thread oldest-first needs--order ascand its own offset loop.get_issuewithinclude_comments='all'returns at most the 500 most recent. - A comment body is capped at 256 KiB, measured after newline and tab normalization. Existing longer comments stay readable.
- Search is bounded and scoped. Page size defaults to 20 and caps at 500, offsets clamp at 100,000, a full-text query over 4 KiB or a literal query over 256 bytes returns 400, and a literal search stops at 10,000 matches. Results from projects the caller cannot see are gone, so counts may legitimately drop; filtering by an invisible project now returns empty rather than an error that revealed it exists.
- Exports are bounded. 10,000 files, 1,000 comments per issue, 100,000 per project, 50,000 metadata items, 8 MiB per file and 128 MiB total. Previously an export asked for at most 10,000 issues and then loaded every comment on each with no limit, so a large project's export was bounded only by available memory; one past the new ceilings now fails with an explicit error. Two run at a time; the rest get 429 with
Retry-After: 30, which an export client should honour. The HTTP download is dropped after 30 seconds idle or 30 minutes in total. - Websocket clients are capped at 16 sockets per user and 1,024 per instance, with 16 KiB messages, 4 KiB frames, 64 messages per 10 seconds, and a 5-second send timeout. Clients that only listen are better off: the server now sends protocol pings every 30 seconds, so they are no longer dropped after 120 seconds for skipping an application heartbeat.
Other behavior changes.
- OAuth access tokens are refused on every credential-management route, which covers API key create/list/revoke, connected-tool management, password change, session revocation, profile edits, user administration, and OAuth client and token management. A connected tool that called those endpoints gets a 403 and needs a browser session or a local CLI command. Ordinary reads and writes through MCP are unchanged.
- Editing or deleting a comment now requires access to its project, not just authorship, on both REST and MCP. Someone removed from a project loses the ability to mutate the comments they left in it.
- Project identifiers resolve case-insensitively across CLI, REST, and MCP, matching modules, folders, and usernames. Creating
abcwhenABCexists is now rejected, and a database that already holds a case collision (only reachable by raw SQL) has them renamed deterministically on upgrade. web_auto_logincannot be enabled on an instance that declares apublic_url. The guard used to look only at the bind host, which passed a loopback bind published through Tailscale Funnel or a same-host proxy. The request is refused with a 400 and not persisted.- The CLI refuses to send an API key over plaintext HTTP to a remote host. What was a warning is an error. Use
https://or a loopback target.LIFIC_TOKENis now sent only to the origin named inLIFIC_URL, so a token plus a--urloverride, or a directory holding alific.tomlfor a different server, falls back to stored credentials for that host. - Uploads are validated by magic bytes rather than the declared content type, capped at 10 MiB by default, and rate-limited to 30 per user per 10 minutes. A client uploading a type outside the allowlist is refused.
- MCP tool errors are generic. Raw database text, which could carry table and column names and the database path, no longer reaches the client; it stays in the server log. A script matching on that text needs another signal.
Upgrade and verify
-
Install the v2.7 binary and restart the existing service.
-
Run
lific doctorwith the operator credential. Resolve configuration, database, backup, server, OAuth, and MCP failures before opening the instance to users. -
Check
lific instance infoand confirm the expected database, public URL, signup policy, andauthz_enforcedstate. -
Review membership and operator keys:
lific key list lific member list --project <PROJECT>Unbound API keys are operator-trusted/admin-equivalent because they can only be minted with shell access. Audit or rotate any key that is no longer needed. Use user-owned bot keys when an agent should inherit project membership.
-
Test the browser login and one representative project workflow. Confirm issue, page, plan, comment, attachment, and saved-view data before testing writes.
-
Test one API and one MCP client. For MCP, refresh tool discovery and replace calls to
export_issue,export_page, orexport_projectwithexport. -
If the instance uses a proxy, confirm the server logs the expected client IP and that rate limits do not trust arbitrary forwarded headers.
-
Keep the pre-upgrade archive until the browser, API, MCP, backups, and service restart have all been verified.
Authorization rollout
An existing 2.0 instance keeps its persisted authorization setting during the upgrade. Inspect it before changing anything. With enforcement on, viewers can read and comment, maintainers can mutate project content and structure, and leads manage settings, membership, and deletion. Cross-project relations and plan-step links require the appropriate role in every project involved.
To change the setting deliberately:
lific instance set --authz-enforced trueBefore enabling it, ensure every human and bot that needs access has a project membership, and keep an operator-trusted key available for recovery. To roll back the setting on a private instance, use --authz-enforced false and then investigate the missing membership or role rather than distributing a broader key.
For a private local deployment, [auth] required = false is a separate setting from project authorization. It grants credential-less operator access and is unsafe anywhere others can reach it; Lific refuses to start when it is set and [server] host is not loopback. Treat both settings as operator changes that require shell access and a verification step.
If the upgrade fails
Stop the service, preserve its logs and the failed database state, and do not repeatedly rerun migrations against the only copy. Restore the pre-upgrade archive to a separate data directory or use lific restore <ARCHIVE> --force only after confirming the archive and intended target. Re-run lific doctor, then compare the restored instance's configuration and client credentials before switching traffic back.