Releases
What changed, and when — written for somebody deciding whether to raise
MOB_VERSION.
How to update: Installing, under Updating. It is one
edit and two commands, and it is worth reading once before the first time.
This page has two copies and they answer different questions. The one you
are reading ships inside the image, so it stops at the release you are running:
it is the history of how you got here. The copy on the website carries the
releases that came out afterwards, which is the half you need before
upgrading. If you are checking whether to move, read that one.
Releases only go forwards, and nothing is skipped by being several behind —
migrations run through every release between yours and the one you raise the
pin to, in the order they were written. Take a backup first, and raise it when
somebody is around to look at the result.
033 — 30 August 2026
One screen, tidied. Nothing to do before or after: no migration runs, no new
environment variable, nobody is signed out, and nothing you have configured
changes meaning.
- Organization Admin opens on work rather than on numbers. Its front page
used to head the list with four counts — workspaces, accounts, and how many
of those accounts were unclaimed or deactivated — under a line explaining
that they were numbers and not doors. They have gone. Anything waiting for
somebody still sits at the top of that page, and the sections beneath it are
unchanged. Whether a particular account has ever been claimed, or has been
deactivated, is still said on that account's own page, which is where
something can be done about it. See
Administering the organization.
032 — 29 August 2026
Integrations grew their missing half: incoming webhooks, and an API that can
set up channels rather than only post into them. Two additive migrations run
on upgrade; nothing destructive, no new environment variable, nobody is
signed out, and nothing you have configured changes meaning.
- Incoming webhooks. An integration can now hold a webhook: a URL that
posts into exactly one channel, for the build server or monitor whose
"webhook URL" field cannot send a header. The URL is the credential — it can
do nothing but post to its one channel, its posts wear the webhook's own
name ("GitHub", "Grafana") over the integration's account, and revoking it
revokes one URL, not the integration. Minted from the integration's page on
the organization's screens. The token never lands in the logs; a pasted URL
is filtered the way a password is. See
Integrations. - The API can arrange channels now, not just post. List a workspace's
channels (rosters included exactly where the integration could already read
them), create a channel and arrive as its admin, add members to a channel it
administers, and look a person up by email address before messaging them.
The lookup answers only for active members of the workspace the token is
asked through, and a miss never says whether an address has an account at
all. Existing tokens gain these without being reissued — it is the same
authorization the web enforces, asked over the wire. - A Python client, in the source tree.
clients/pythonis a
dependency-free client for the whole API with runnable examples, for anybody
who would rather start from working code than fromopenapi.yaml. It ships
in the repository, not in the image. - Every message hands out its own link. Copy link on a message's menu
puts a permalink on the clipboard; opening one lands in the channel at that
message, and a reply opens its thread beside it — exactly as a search hit
does. - An image on the clipboard becomes an attachment. Paste into the
composer and it uploads as if attached, named so a screen reader has
something better to say than a timestamp. - The words match the model now. Screens, docs and the installer speak of
the organization (its people, workspaces and policy) and the
installation (the machine, its release and its backups) — and the first
account the installer creates introduces itself as the organization's owner
rather than a "Super Admin". Wording only: no path, permission or setting
moved.
031 — 21 August 2026
Sessions can now be ended on purpose — by the person who owns them, and by
whoever administers the organization. Nothing to migrate, no new variable, and
nobody is signed out by upgrading.
- Everybody can see where they are signed in. The last section of
Preferences lists every browser on the account: what it is, when it signed in,
how, and the address it came from, with the one being read marked. Sign out
ends one; Sign out everywhere else ends the rest and keeps the browser in
hand. Point people here first — it is the door that needs nobody's help. See
Your account. - Sessions have no expiry, and that is still deliberate. A credential that
stops working at three in the morning is one nobody revoked and nobody can
see. But until now nothing could end one on purpose either: signing out ended
the browser you were in, and everything else that severed a session did it as
a side effect of something larger. A laptop left on a train was somebody
else's errand. This is the release that makes it an act. - Two controls for the organization. Sign out everywhere on an account's
own page, saying how many browsers it has and since when; and a Sessions
section for the whole installation at once. Ending a session changes nothing
about the account — the credential they had is the one they sign back in with
— so it closes a door without changing the lock. See
Administering the organization. - Integration tokens are deliberately not included. A bearer token is a
session too, so an unqualified sign-out would stop every build server and
monitor you run as a side effect of a button about people. Revoke those one at
a time under Integrations, where each is named and can be rotated without an
outage. Deactivating an integration's account still takes everything it holds. - The session doing the signing-out is spared, at both scopes. The authority
that would undo a mistake here is the one being ended, and where a directory
owns that address the way back in runs through the provider that may be the
reason for the click. - A correction in the user guide. It sent anybody who had forgotten their
password to "the link on the sign-in page". There is no such link and no
self-serve reset in Mob at all — resets are issued by an admin, which is what
lets them work on an installation that cannot send mail. Anyone who went
looking was hunting for something that has never existed.
Under the surface, and worth one line for anybody who audits this sort of
thing: what Mob serves to a stranger is now pinned by two tests rather than by
grepping. Ten controllers and twenty-one actions are written down with the
reason each is public, and every route the app answers is swept with an
unauthenticated request to prove the door is shut by behaviour and not only by
declaration. Nothing about the public surface changed; it is now checked.
030 — 21 August 2026
A permission moved. Integrations are the organization's now, not each
workspace's, so a workspace admin can no longer make one or see its tokens.
Nothing to migrate and no new variable; every integration you already have
keeps working, in the same channels, with the same tokens.
- Integrations are made once, for the organization, and switched on per
workspace. Making one mints an account and a credential that outlives every
session, which is the organization deciding who exists — the same family as
inviting somebody, and not a thing a single room should settle. A workspace's
Integrations screen therefore makes nothing: it lists what the organization
has and each admin decides whether it may act here. If the one they want
is missing, they ask, and it appears switched off for them to switch on. See
Integrations. - Switching one off is not removing it, and it stops the integration in that
workspace only. The channels it was in are remembered, so switching it back on
a fortnight later puts it back where it was instead of handing somebody six
channels to reconstruct; and its tokens keep working everywhere else, because
a token is not one workspace's to revoke. - A security fix worth naming. An integration — or a person — deactivated in
a workspace could still post there over/api/v1. Being switched off was
enforced on the way in through the web and not on the way in through the API,
which is exactly the door a long-lived token uses. Both now answer at the same
seam. If you have ever switched somebody off in a workspace while they held a
token, this is the release that makes it stick. - One name is one integration. Two workspaces that each wanted a deploy bot
used to get two unrelated accounts with two sets of tokens; addresses already
minted keep the shape they have, and nothing is routed to them either way. - The organization says when the owner password is still in
.env. While
MOB_ORGANIZATION_OWNER_PASSWORDis set it is re-applied on every sign-in
attempt rather than spent once — which is deliberate, because no
administrator can mint an owner's credential and editing.envhas to be the
way back in. But it means the owner cannot durably change their own password
while the line stands, and the highest authority here has a password readable
by anybody who can reach the file. It now appears under Needs you, naming
the address it overwrites. Remove the line once you are in, and restart. - A quotation ends at the end of its line. Markdown's rule carries an
unmarked line into the quote above it — a rule for prose hand-wrapped in a
text editor, not for a chat box that wraps itself, where every newline was
typed on purpose. A reply written under a quoted line used to arrive behind
the same grey bar. It is now a paragraph of its own, which is what Slack does
and what people typing here expect. Quoting several lines still needs a>on
each, which is what the Quote button writes.
029 — 21 August 2026
Mob looks different after this one, and that is the whole of what your
people will notice. Nothing to migrate, no new variable, no migration that
takes anything away.
- The app is set in Plus Jakarta Sans, where it used to be Open Sans.
Anybody who chose a typeface in Preferences keeps the one they chose — Open
Sans included, which is still on the list — so the readers who notice are the
ones who never picked. The face ships in the image as it always has: no CDN,
no new outbound host, nothing to allow through a firewall. The typeface menu
is in alphabetical order now, with the default at the top. - Form controls stand at one height. On the admin screens that carry a
small embedded form — invite somebody, mint a join link, add an integration,
put an account into a workspace — the field, the dropdown and the button each
used to be drawn at a slightly different height, so the row stepped and the
labels stepped with it. Worst in Safari, where the dropdown on Join links sat
a clear line below the field beside it. Sixty-one controls across fifteen
rows now agree. - The published image no longer carries our own build and deployment
files. They never ran in your container — a Kamal configuration, the
development compose file, the Dockerfile, the tasks we use to cut a release
and publish the website. Everything the manual tells you to run is still
there and unchanged:backup:*,slack:*,install:first_runand
push:keys. You should notice nothing except a slightly smaller pull.
028 — 21 August 2026
There is no 27 image. That release was tagged, its build failed before the
publishing step, and nothing was pushed to the registry — so MOB_VERSION=27
would find nothing to pull. Everything the entry below describes arrives here
instead: if you are on 026, take this one and read both entries.
- A test could kill its own worker, which is how 027 failed to publish.
Rake tasks are additive — loading them twice appends a second copy of each
task body rather than replacing it — so a task that ran itself twice walked
into its own guard the second time and stopped the process rather than
failing a test. Nothing an installation ever ran, and nothing in the image:
this is Mob's own suite, and it is here because a release that publishes
nothing is worth explaining rather than skipping over.
027 — 21 August 2026
A fix to 026 worth taking straight away if you are on it, and an updater so
that taking it is one command.
- Updating is
sh update.shnow. The installer leaves it beside the stack;
if you installed before this release, fetch it once with
curl -fsSL --connect-timeout 15 https://jojospacebear.com/install/update.sh -o update.sh.
It asks the registry which release is newest, tells you which one you are
running, points at these notes, and does nothing until you say yes — then
raises the pin, pulls, restarts, and waits to see the app answer. If it does
not answer it prints the log command and the two lines that put you back.
It replaces itself with a fresh copy before each run, because the steps for
moving onto a release are the ones shipped with it. - An Org Owner could not give themselves a role in a workspace they had
just joined, and got a message aboutMOB_ORGANIZATION_OWNERSfor their
trouble. The rule that stops administrators unseating each other was refusing
the owner it was written to protect. Owners may do anything an interface
offers; the one authority no screen hands out is another owner, and that is
named in the environment rather than withheld by a rule. - A tier now holds everything the tier below it holds, as a property of the
permission model rather than of two lists kept in step by hand. That is what
the bug above really was, and this is what stops the next one: an
administrator can no longer be given a word the owner above them lacks. restore.shwas too easily convinced it had found an installation. It
took acompose.yamlbeside a.envas proof — and a development checkout
has both, for a stack with no Mob in it. On a machine with a checkout on it,
a script that can replace a database was pointing at the wrong directory.
Both it and the new updater now require the app's own image to be named in
the compose file. Nothing to do; it only ever narrows what they will act on.
026 — 21 August 2026
Administering Mob is now organised around the organization: the whole
installation, one company, with its workspaces as rooms inside it rather than
tenants beside each other. Almost everything in this release follows from
naming that properly.
Two things to do before you upgrade, and one to expect.
Name your owners in .env and restart:
MOB_ORGANIZATION_OWNERS=you@example.com
MOB_ORGANIZATION_OWNER_NAME=Your Name
MOB_ORGANIZATION_OWNER_PASSWORD=choose-something-long
Mob runs without them — you simply have nobody who can appoint an
administrator until they are there. If you deploy with Kamal, deploy.yml
must name them too, because Kamal sends only what it names.
This upgrade empties the event log. The log gained a new vocabulary with
the rename, and half a log speaking a retired word is worse than a clean one;
nothing is rewritten, the table is emptied whole, and the first line of the new
log records that it happened. If the history matters to you, take a backup
first — which is worth doing anyway.
- Super Admin is now Organization, at
/org, and it has two tiers. An
Org Owner is named in.envand is the one authority no screen in Mob
can hand out or take away. An Org Admin is granted by an owner, on the
account's own page, and does everything else. That is what makes appointing
administrators safe to do from a screen at all: a grant needs somebody who
cannot be appointed by the people they are appointing. The console command
that used to do this is gone. - A security fix worth naming. Issuing a password reset never asked who the
target was — so a workspace admin who could reset passwords could issue one
for an administrator who happened to be a member of their workspace, spend
it, and take over the installation. Credentials are now handed downward only:
minting or rewriting somebody's login takes at least their authority. The
same rule covers editing an account's email address, which is what people
sign in with. - The first account is made from
.env, on any installation. It used to be
a console command or an installer prompt piping a password over stdin; both
are gone. This is also how you get back in if the password is ever lost — no
administrator can reset an owner's, so the environment has to be the door:
change the password line, restart, sign in, and delete the line afterwards. - Inviting people is an organization job now. An invitation mints an
account — an address that had none, given one — which is the organization
deciding who exists rather than a room arranging who is in it. Workspace
admins keep adding people who already have accounts, under People; requests
to join, invitations and join links moved behind the organization role, and
the queue is visible from Organization → Workspaces with a count on it. - Who somebody is is edited in one place. The workspace screen that edited
a member's name, email and connected accounts is gone; all of it is on the
account's page under Organization → Accounts. A name and an address follow a
person into every room, so they were never a room's to change. - An organization role can now read any conversation, and the read is
recorded. Reading used to take a membership even for administrators, so
answering a question about a private channel meant joining it and appearing
on its roster. Now it does not — and opening a private channel or a direct
message you are not in writes a line in the event log saying who, which
channel and when. A workspace admin's reach is unchanged: they still
administer a private channel without being able to read it. - An About page, under Organization, answering the questions that used to
need a shell: which release this is, what Ruby and Postgres it runs on,
whether jobs are threads inside Puma or their own container, whether mail and
single sign-on and backups are configured, and every environment variable Mob
reads. Secrets say whether they are set and never what they are. - The roster says Org Owner and Org Admin, in those words, because every
roster in Mob has admins and a workspace's is a much smaller thing. - The member picker searches the whole organization. It used to ask for a
whole email address unless a directory was configured, which was a guard
against one company listing another's staff on a shared installation. That is
what separate instances are for, so the guard is gone and the picker is a
picker.
025 — 20 August 2026
Mob will now tell you whether it is coping, on the screens the people who would
fix it already have open.
Nothing here needs configuring, and nothing here is switched on by asking
anywhere outside your installation. One new job runs every five minutes.
- Analytics, on the organization's screens, in two halves: what people are doing (messages
and files per day, who posted this month, who is online, which workspaces are
busiest) above, and what it costs (the job queue, the object store, the four
databases) below. Almost nothing is stored to draw it — most of it is counted
from rows that already carry their own timestamps. See
Administering the organization. - Analytics for each workspace too, showing that workspace's month to its
own admins: messages a day, how many different people posted, who joined, and
what is being uploaded. Direct messages are counted and never named — every
DM in the workspace is one unlabelled line. Nothing comparing one workspace
with another appears there; that stays yours. See
Setting up a workspace. - Failed jobs are listed, with Retry and Discard, which previously took a
console. Each row warns what running that job a second time would actually
repeat — a duplicate push notification, say — rather than a generic
are-you-sure, and the jobs that are safe to repeat say nothing at all so the
one row that matters is not buried. - The queue is judged by waiting time, not depth. A thousand jobs worked off
in four seconds is a healthy queue and forty that have waited nine minutes is
a stuck one; the number waiting cannot tell those apart. It also names the two
states that look like an idle queue and are not: no workers running, and a
queue somebody paused. - Tell it how large your storage volume is — a box on that page, in
gigabytes — and the meter gains a ceiling and the upload chart gains a date it
will be full. Mob cannot measure this itself and will not guess: the store is
an S3 endpoint that may be a container here, a NAS, or somebody else's region.
Left blank, you still get what is stored and how fast it is growing, with the
projection withheld rather than invented. - A gap in a chart is drawn as a gap, never as zero, because "the queue was
empty" and "nobody could see the queue" are opposite claims. The five things
that leave no trace behind them — queue depth and wait, failed jobs, people
online, bytes stored — are sampled every five minutes and kept for thirty
days. That is fixed rather than a setting; the table has a ceiling by
construction. - Days on these charts are cut in the installation's time zone rather than
each reader's, which the page names at the foot — a bar labelled Tuesday has
to hold Tuesday's messages for everybody looking at it. - "Jump to latest" no longer drops you back into last week. 024 fixed this
for a press that caught a page of history still on its way; a press that
caught one that had just arrived still took you to the newest message and
then pulled you back up to where you had been reading. Both halves are the
same fault and both are now fixed.
024 — 20 August 2026
Reading back through a channel now offers the way out of it. Nothing to
configure, and nothing changes for anyone sitting at the bottom of a channel.
- "Jump to latest" appears as soon as you scroll off the bottom of a
channel. It used to appear only in the narrower case where Mob had not yet
loaded the newest messages, which meant that scrolling back through a
conversation you were already caught up on offered no way down again except
scrolling all the way, and nothing on the page said you had left the present
at all. One press returns you to the newest message, however far back you
have gone. - Pressing it while earlier history was still loading used to put you at
the bottom and then pull you back up into the middle of last week: the page
arriving carried a scroll correction measured before you pressed, and it was
applied after you had left. Pressing now cancels whatever is in flight, so
the button behaves the same whether or not you caught it mid-load.
023 — 19 August 2026
There is no 022 to run. It was tagged and the image never built — a check
failed before the publishing step — so nothing was released under that number.
Everything listed under 022 below is in this release; read it as one.
What this adds on top is all in restore.sh, and both parts were found by
running it on a real machine rather than by reading it:
- It finds your installation. The installer creates its directory relative
to wherever it was run, so an install done as root lives in/root/mob— and
a later shell belonging to somebody else cannot read through a0700 /root
to see it. It used to look in two places and say "no installation here",
which was unhelpful and untrue at once. It now looks where installations
actually land and then asks Docker, which stamps the project's directory into
every container and so knows wherever the stack exists at all.
Found-but-unreadable is its own message, namingsudo. - Diagnosis is free again, as the script always claimed. It used to be free
only if you already had backups: noBACKUP_S3_ENDPOINTand it stopped
before printing anything, so the installation with no backups — whose owner
most needs telling what state it is in — was told nothing at all.
Unconfigured, half-configured and unreachable destinations are lines in the
report now, with a warning under them. Restoring still needs a destination;
being told what is wrong does not. - The app's last log lines print with the report when it is down. That is
the most useful thing on the machine and the one thing you cannot go and read
for yourself,/helpbeing served by the container that is not running.
022 — 19 August 2026
Admins got a way to say something that stands, the app got an account to say
things from, and installing stopped ending at a command to paste.
Nothing in this release contacts anything outside your installation. The update
check it adds is switched off until you turn it on.
- Announcements: a coloured banner or card across the top of the app,
standing until it lapses or is taken down. Workspace admins write them for a
workspace or a channel; an organization admin can address the whole organization, or
one person wherever they are. Tone, banner-or-card and whether readers may
dismiss it are each a choice, and anything undismissable must carry an end
date. See Announcements. - The app has an account to speak from, and uses it: an import that
finished while you were elsewhere, a backup that failed at 3am, and an
installation with no off-host copies now say so in a direct message rather
than only on a screen nobody had open. It appears on no roster and in no
picker, and cannot be deleted or given a token. - The installer makes your first account and your first workspace. It asks
before it asks anything about an account whether this is a fresh
installation or a machine you are rebuilding to restore a backup onto — a
restore brings its own accounts, and one made here would be dropped by it.
The password is typed with the terminal's echo off and never reaches your
shell history. See Installing. restore.shreports the release, because a restore ends by starting the
stack and that recreates the app from whateverMOB_VERSIONnames. If the
container that was running is one release and.envnames another, restoring
and starting changes the app's version as well as its data — so it says both,
and asks again before starting a release you were not on.- The worker waits for migrations it does not run. Only the web container
migrates; on the four-container setup the worker used to start draining the
queue with new code while the schema was still changing under it, and jobs
that failed in that window stayed failed. This affects anyone running with
MOB_JOBS_HOSTset, and needs nothing from you. - Vimeo, X and Mastodon link cards, in two families: a video draws a still
and a play button and loads the player only when pressed, while a post draws
the author and words and loads nothing at all, ever. - Joining a channel no longer closes Browse, so picking several is one
visit instead of several. - Updates, on the organization's screens: which release you are running, whether a newer
one has been published, and a link to what changed. Checking is off until
you switch it on, and until then Mob contacts nothing — this would be the
only thing in it that ever talks to anywhere outside your installation on its
own, so it waits to be asked. Switched on, it asks the registry you already
pull images from once a day, and organization admins get a message the first time a
release appears; the request carries no identifier, no counts, and nothing
about your workspaces or the people in them. The screen also reports a
database migrated by a newer release than the image running, which is what
loweringMOB_VERSIONleaves behind and nothing previously noticed. - This page, which did not exist. Its copy on the website carries releases
newer than the one you are running, which is the half worth reading before
upgrading.
021 — 19 August 2026
Backups stopped being something you configured and hoped about.
- Backups have a page on the organization's screens, with the last dump, the last mirror
and the last restore check, and a trail in the event log. Terminal outcomes
only — an attempt that will retry is not yet an outcome. restore.sh, which reports what state the stack, the database, the files
and the destination are in before it offers to touch any of them, and puts
the destructive step behind the database's name typed back. See
Backups.- Backup jobs retry before they alarm, and alarm once, so a blip that
healed itself is not news. - The weekly check asks whether the destination remembers — bucket
versioning off means a deleted attachment's mirror copy is gone for good, and
now it says so. - Rich link cards for YouTube, drawn as a facade: our stored still and a
play badge, with the player appearing only when somebody clicks. No provider
is contacted by scrolling past a card. - New installs pin themselves to the release they fetched, so nothing moves
until you raiseMOB_VERSION. - Every fetch from the site is bounded at fifteen seconds of not connecting.
020 — 18 August 2026
Importing from Slack became a screen instead of a terminal session.
- Import a Slack workspace from the organization's screens, without a checkout and without
a shell — upload the export, review the roster, run it. The two-pass shape is
unchanged, and the pause in the middle is still the point. See
Importing from Slack. - The installer installs Docker where the machine has not got it, so a bare
Debian, Ubuntu, Fedora or RHEL-family image is a complete starting point. - Blob storage is reachable under its own public name from the server as
well as the browser, and the app now fails loudly at boot when it is not
rather than serving broken attachments. - A Slack file imports once however many times the import runs.
- Three more fonts: Source Sans 3, Plus Jakarta Sans and Rubik.
019 — 18 August 2026
- Post times are drawn in the reader's own timezone, with the day divider
derived the same way — so one instant reads 09:00 in London and 18:00 in
Tokyo, and the heading above it agrees. - A back button on the phone, sized so the channel name sits over the
feed's text column. - The composer is drawn as one box rather than three stacked rows.
- Both ends of the feed fade instead of being cut.
018, 017, 016, 015, 014 — 16–17 August 2026
A run of small releases about the same thing: what the sidebar and the feed do
when the connection drops and comes back.
- What arrived while the socket was down is fetched on coming back, rather
than leaving a channel quietly missing messages until a reload. - Unread badges the sidebar slept through are asked for on reconnect.
- A message is marked read only where somebody could actually have seen it.
- Presence is no longer presumed the moment the app goes behind something else.
013 — 16 August 2026
The event log, and the first published install bundle.
- An append-only event log, written in the writer's own transaction from
model callbacks — so no write path, web or console or API, can make a change
without its record. Payloads carry facts by value, because the log has to
outlive its subjects. - Retention, as an organization screen: how long events are kept, and how
long a removed attachment's file survives. The default is to keep events for
ever, because the absence of a decision must not destroy evidence. - The log never deletes its own account of itself — shortening retention
cannot erase the entry showing it was shortened. - Removing an attachment stops destroying the file. The blob outlives the
message and is collected later, so the log's record of it does not point at
nothing. - The install bundle is published, so installing means fetching an image
rather than building one.
Before 013
This project's git history was rebuilt in July 2026 and the releases before
this point are not usefully reconstructable. They are the ones nobody is
running.