Skip to content

wekan

v9.96 Breaking

This release includes breaking changes for platform teams planning a safe upgrade.

Published 10d Productivity & Wikis
✓ No known CVEs patched
Read the diff → Tool health → What is this tool? →

✓ No known CVEs patched in this version

Topics

docker javascript kanban meteor real-time sandstorm
+2 more
snapcraft wekan

Affected surfaces

deps

Summary

AI summary

Updates https://github.com/wekan/wekan/issues/6458, https://github.com/wekan/wekan/issues/6466, and https://github.com/wekan/wekan/pull/6462 across a mixed release.

Changes in this release

Feature Medium

Introduces general `cpu-exec` helper with bundled qemu-user for CPU feature emulation.

Introduces general `cpu-exec` helper with bundled qemu-user for CPU feature emulation.

Source: llm_adapter@2026-07-16

Confidence: high

Feature Low

Adds Sandstorm .spk package to releases (non‑blocking).

Adds Sandstorm .spk package to releases (non‑blocking).

Source: llm_adapter@2026-07-16

Confidence: high

Dependency Low

Bumps websocket-driver from 0.7.4 to 0.7.5.

Bumps websocket-driver from 0.7.4 to 0.7.5.

Source: llm_adapter@2026-07-16

Confidence: high

Performance Low

Reduces CPU usage of FerretDB by throttling Meteor polling intervals and limiting connection pool size.

Reduces CPU usage of FerretDB by throttling Meteor polling intervals and limiting connection pool size.

Source: granite4.1:30b@2026-07-16-audit

Confidence: low

Bugfix High

Resolves LDAP org/team sync causing all LDAP logins to be forbidden.

Resolves LDAP org/team sync causing all LDAP logins to be forbidden.

Source: granite4.1:30b@2026-07-16-audit

Confidence: low

Bugfix Medium

Restores “More” menu on cards, allowing deletion and copy‑link actions.

Restores “More” menu on cards, allowing deletion and copy‑link actions.

Source: llm_adapter@2026-07-16

Confidence: high

Bugfix Medium

Enables clearing “unsaved description” warning by properly saving drafts.

Enables clearing “unsaved description” warning by properly saving drafts.

Source: llm_adapter@2026-07-16

Confidence: high

Bugfix Medium

Prevents loss of in‑progress comment when card is closed or clicked outside.

Prevents loss of in‑progress comment when card is closed or clicked outside.

Source: llm_adapter@2026-07-16

Confidence: high

Bugfix Medium

Eliminates “Removed nonexistent document” client error when deleting attachments.

Eliminates “Removed nonexistent document” client error when deleting attachments.

Source: llm_adapter@2026-07-16

Confidence: high

Bugfix Medium

Restores rule actions that move cards to top/bottom of lists.

Restores rule actions that move cards to top/bottom of lists.

Source: llm_adapter@2026-07-16

Confidence: high

Bugfix Medium

Corrects desktop density regressions from mobile UI changes (font sizes, card layout).

Corrects desktop density regressions from mobile UI changes (font sizes, card layout).

Source: llm_adapter@2026-07-16

Confidence: high

Bugfix Medium

Fixes subtask creation crash caused by removed sync Meteor APIs.

Fixes subtask creation crash caused by removed sync Meteor APIs.

Source: llm_adapter@2026-07-16

Confidence: low

Bugfix Medium

Prevents endless 502 Bad Gateway when mongod cannot start due to missing AVX on CPUs.

Prevents endless 502 Bad Gateway when mongod cannot start due to missing AVX on CPUs.

Source: granite4.1:30b@2026-07-16-audit

Confidence: low

Bugfix Medium

Handles partial FerretDB migration failures without deleting the database, avoiding 502 errors.

Handles partial FerretDB migration failures without deleting the database, avoiding 502 errors.

Source: granite4.1:30b@2026-07-16-audit

Confidence: low

Bugfix Medium

Fixes double‑escaped LDAP group search filters causing filtering failures.

Fixes double‑escaped LDAP group search filters causing filtering failures.

Source: granite4.1:30b@2026-07-16-audit

Confidence: low

Full changelog

v9.96 2026-07-16 WeKan ® release

This release adds the following updates:

  • Releases now include the Sandstorm .spk package (.github/workflows/release-all.yml).
    The release-all workflow got a new build-sandstorm job that runs after the GitHub Release
    is created: it builds the Sandstorm package from the release tag with the same steps as the
    standalone sandstorm.yml workflow and attaches wekan-<version>-sandstorm.spk to the
    Release. The job is non-blocking (continue-on-error), so the experimental Sandstorm build
    never fails or delays the rest of the release. The standalone sandstorm.yml workflow still
    exists for building and testing the .spk on its own without doing a full release. Thanks
    to xet7.

  • Bump websocket-driver from 0.7.4 to 0.7.5.
    Thanks to dependabot.

  • New general cpu-exec + bundled qemu-user: every WeKan platform now runs binaries that need
    missing CPU features through emulation automatically

    (#6458, snap-src/bin/cpu-exec,
    snap-src/bin/mongodb-control, snap-src/bin/migration-control, .github/workflows/release-all.yml,
    .github/workflows/sandstorm.yml, sandstorm-src/build-deps.sh, releases/ferretdb/start-wekan.sh,
    releases/ferretdb/wekan-entrypoint.sh, docs/Databases/mongodb-avx-qemu.md,
    docs/Databases/mongodb-raspi4-qemu.md). The #6458 report turned out to run inside a hypervisor
    that MASKS AVX — and the snap's old per-tool AVX wrappers (amd64-only, PATH-based) were bypassed
    by every absolute-path mongod invocation. The new cpu-exec helper is one general mechanism
    for all scripts, sandboxes, platforms and CPUs: cpu-exec --features x86_64=avx,aarch64=atomics <binary> [args] checks /proc/cpuinfo and, when a required feature is missing, transparently
    re-runs the binary through a same-architecture qemu-user (bundled first, then system); with no
    declared features it is a plain zero-overhead exec, so every binary can be routed through it
    (WEKAN_REQUIRED_CPU_FEATURES declares requirements externally). The Snap's mongodb-control and
    migration-control now run every mongod 7 through it — so MongoDB works (slower) on CPUs without
    AVX and the migration can READ modern MongoDB data there, with the FerretDB switch/migration
    fallbacks unchanged; aarch64=atomics covers MongoDB's ARMv8.2-A requirement (Raspberry Pi 4
    and older lack it). release-all.yml now ships cpu-exec plus this-arch's static qemu-user in
    every Linux bundle .zip (amd64/arm64/ppc64le/s390x/riscv64 — stripped from the Windows/macOS
    bundles, where qemu-user does not exist), which flows into the Docker image and the Snap
    automatically, and the Sandstorm .spk gets both via build-deps.sh; the bundle launcher and
    Docker entrypoint route node/ferretdb through it. Thanks to a1bert01 and xet7.

  • Added regression tests, with negative cases, for all of the fixes below (tests/htmljsArrayContent.test.cjs,
    tests/cardDescriptionDraft.test.cjs, tests/commentDraft.test.cjs, tests/attachmentDeleteGuard.test.cjs,
    tests/ruleMoveAction.test.cjs, tests/snapMigrationRecovery.test.cjs, tests/ferretdbPolling.test.cjs,
    tests/uiDensity.test.cjs, tests/cpuExec.test.cjs — a BEHAVIORAL test that executes the real
    cpu-exec against fake /proc/cpuinfo files and a fake qemu-user, covering direct exec, qemu fallback,
    missing-qemu error, per-arch scoping and env overrides —, tests/cpuExecWiring.test.cjs — pins the
    cpu-exec DELIVERY pipeline: every Linux bundle in release-all.yml embeds cpu-exec plus its own
    arch's qemu-user (arm64/extra arches replace the inherited amd64 one, tolerantly), the Windows and
    macOS bundles strip both, qemu-user-static is installed in every bundle-building job, the Sandstorm
    .spk ships both via build-deps.sh, the Docker entrypoint and bundle launcher route ferretdb/node
    through cpu-exec WITH direct-exec fallbacks for bundles that lack it, and the snap ships it via the
    snap-src helpers part —, tests/subtasksDefaultBoard.test.cjs (see the #6456 entry below), and tests/ferretdbHasData.test.cjs — a BEHAVIORAL test executing the real snap-src/bin/ferretdb-has-data guard (the check that gates every switch to FerretDB) against crafted directories: non-empty .sqlite passes with no -wal sidecar required, while a 0-byte .sqlite from a failed migration, sidecar-only leftovers, a directory named *.sqlite, and empty/missing directories are all rejected — all
    wired into test:unit:node in package.json; plus Go table tests in the wekan/FerretDB fork's
    internal/backends/sqlite/query_test.go and the fork's integration-test fixes (OTel exporter
    skipped with a single log line when no collector is listening, and valid span contexts without a
    collector so TestOtelComment passes — details in the fork's own CHANGELOG Upcoming). The htmljs test exercises the vendored
    compiler's Tag constructor directly (array content vs. attributes, #6459) and the draft tests run the real
    extracted normalize/normalizeTrigger functions; the UI tests guard the #6465 density fixes in the repo's
    CSS-guard style (base font 14px, card details docking right of the board instead of over its own card, compact
    admin table headers, un-clipped zoom pill, one-click board settings cog). The FerretDB Go tests pin the SQLite
    filter-pushdown semantics: which strings are pushdown-safe, exact WHERE/args for _id and top-level equality
    filters, and that dotted paths, operators, non-strings and unsafe strings stay with the in-Go filter. Thanks
    to xet7.

and fixes the following bugs:

  • Subtasks: creating a subtask crashed with "Exception while invoking method 'addSubtaskCard'"
    (#6456, models/boards.js,
    tests/subtasksDefaultBoard.test.cjs). The lazily-creating getters for the default subtasks
    helper board and its landing list still used the SYNC Boards.insert / Swimlanes.insert /
    Lists.insert / Boards.update APIs, which Meteor 3 removed from the server — so the
    addSubtaskCard method's async path crashed with "insert is not available on the server.
    Please use insertAsync() instead"
    the first time a board needed its ^Board^ helper board
    created (the list creator additionally read getDefaultSwimline()._id, which on the Meteor 3
    server is a Promise, so it could never have worked). The sync getters are now PURE (no
    creation — also matching the #3868/#2256 rule that only the server may create these), and the
    server-side lazy creation lives in getDefaultSubtasksBoardAsync/getDefaultSubtasksListAsync
    using the async APIs. The never-called date-settings twins, which had the same sync calls and
    no server-only guard at all, are pure getters now too. With a regression test guarding that no
    sync collection writes come back to models/boards.js. Thanks to sjohnen and xet7.

  • GUI: the "More" menu of cards was completely empty, so cards could not be deleted from it
    (#6459,
    npm-packages/meteor-jade-loader/lib/vendor/htmljs.js, client/components/lists/listHeader.jade).
    A WeKan-local vm-sandbox patch in the vendored jade compiler's htmljs made
    isConstructedObject(Array) return false (upstream returns true), so a tag whose inline text
    compiles to an ARRAY — the label {{_ 'source-board'}}: and label {{_ 'parent-card'}}: lines in
    cardMorePopup — got its content array mis-assigned as the tag's attributes. At runtime Blaze then
    found a template view object inside the attributes and threw "The basic TransformingVisitor does
    not support foreign objects in attributes"
    on every render, killing the whole popup — including
    the card Delete link. The tag constructor now treats an array first-argument as content, like
    upstream htmljs. Also fixed the list "More" popup's copy-link input, which was always empty because
    it referenced a rootUrl helper that does not exist (now absoluteUrl). Board and swimlane menus
    intentionally offer archive (delete lives in Sidebar → Archive), so only the card menu was actually
    broken. Thanks to thku and xet7.

  • Cards: the "You have an unsaved description" warning could never be cleared by saving
    (#6455, reincarnation of
    #1287, client/components/cards/cardDetails.js,
    client/components/cards/cardDescription.js). Two bugs: closing the description editor only
    avoided adding a draft when the text matched the saved description — it never removed a
    pre-existing draft record, so once the warning appeared, "View it" → Save could not clear it, only
    Discard could. And the comparison matched a per-line-whitespace-stripped draft against the raw
    stored description (or null when empty), so descriptions with Markdown " " hard-breaks
    re-created a phantom draft on every save. Saving now removes the draft record explicitly, and both
    sides of the comparison are normalized the same way. Thanks to C0rn3j and xet7.

  • Comments: a comment being written was lost when the card was closed or a click landed outside
    the card
    (#5547,
    client/components/activities/comments.js). The comment-draft machinery existed (the form even
    prefills from it) but had been disarmed since 2019: the escape handler that saved the draft was
    gated on a "form is open" flag that nothing ever set — the setter was removed back then because the
    handler also cleared the visible text on every outside click. Now the draft is saved continuously
    (debounced) while typing and flushed when the form is torn down, submit removes the draft, and the
    escape handler no longer clears the visible text — so an unfinished comment survives closing the
    card, and reopening the card restores it into the form. Thanks to Finnlife, webenefits and xet7.

  • Attachments: deleting an attachment could log a client-side "Removed nonexistent document"
    exception even though the delete succeeded

    (#5282,
    client/components/cards/attachments.js). Same class as the fixed #3252 for comments and
    checklists: under publication churn the attachment document can already be evicted from Minimongo
    when the confirm handler runs, and removing a missing _id throws. The delete now only runs when
    the document is still in the local cache — the comment/checklist guards' missing sibling. Thanks
    to lupuszr and xet7.

  • Rules: the "move card to top/bottom" actions did nothing
    (#6472, server/rulesHelper.js,
    client/components/rules/actions/boardActions.js, client/components/rules/rulesImportExport.js,
    server/rulesButton.js, models/lists.js). A pile-up of five bugs, all silent because the
    activity hook swallows rule-action errors: (1) an unresolved destination list (typo'd, renamed,
    case-mismatched, or on another board) crashed on list.cardsUnfiltered — now it falls back to the
    card's current list; (2) the classic rule wizard's generic "move to top/bottom" stored the field as
    listTitle, which the rule engine never reads (listName) — so every such rule created from the
    wizard has never worked; (3) an empty destination list made Math.min()/Math.max() of nothing
    write a corrupt sort: ±Infinity; (4) the rules JSON/CSV import created rules with raw client
    inserts that the board-admin-only allow rules reject into minimongo limbo — it now uses the same
    rules.createRule server method as the wizard, and defaults missing trigger matching fields (e.g.
    userId) to the * wildcard so hand-written JSON matches; (5) rules.createRule let an empty
    boardId: '' from a not-yet-loaded board selector override the real board. Also fixed the
    server-side orphaned-cards fallback in List.cards()/cardsUnfiltered(), which silently never
    applied because an async lookup was read synchronously. Thanks to jmb26240 and xet7.

  • GUI: desktop density regressions from the v7.98–v8.18 mobile UI work
    (#6465, client/components/main/layouts.css,
    client/components/cards/cardDetails.css, client/components/main/header.css,
    client/components/settings/peopleBody.jade, client/components/settings/settingBody.css,
    client/components/boards/boardHeader.jade). Restores the WeKan 6.09 desktop look users asked
    for: the base font is back to 14px (the clamp(...2.5vw...) introduced in v7.98 and raised in
    v8.02 resolved to 18px on any window wider than 720px — +28.5% on every font, button and input,
    the "everything is way too big" complaint) and headings back to 22/18/16px; the card details
    window no longer opens as a huge floating sheet ON TOP of its own card — it docks to the right
    edge like the classic side panel (still movable by its drag handle), with 6.09's 20px content
    padding instead of ~48px white borders, a 3px corner radius, and without the v8.18 rule that
    forced ALL card text to the title's size; the All Boards page header band is back to 6.09's
    compact padding; the Admin Panel Organizations/Teams tables no longer explode column widths
    ("Select all / Unselect all" header links are now compact icons and header cells may wrap); the
    zoom pill no longer renders cut off (fixed-pixel pill inside the 28px quick-access row); and
    board settings opens with ONE click from a new cog button in the board header (the sidebar path
    still works). Follow-ups caught by the Playwright suite: archiving or deleting a card now also
    CLOSES its details window (the card id stayed in the openCards session list, so the right-docked
    window kept rendering exactly over the archives sidebar and intercepted its Restore/Delete
    clicks), and an OPEN sidebar now stacks above the card window (z-index 2002 vs 2001) so the
    sidebar is always usable while a card is open. With Playwright coverage: a new spec proves board
    settings opens in one click from the header cog (and that the popup closes again), and the
    archives spec's board-menu click is scoped to the sidebar instance since the cog made the bare
    class selector ambiguous. Note: a missing watch "eye" icon is the Admin Panel → Features →
    Notifications "disable watch" setting, not a regression. Thanks to Mintyt, csonkaoszimt,
    micha141076 and xet7.

  • Snap: WeKan served "502 Bad Gateway" forever when mongod could not start — including on CPUs
    without AVX ("Illegal instruction")
    (#6458,
    #6466, snap-src/bin/mongodb-control,
    snap-src/bin/migration-control). MongoDB 5.0+ x86_64 binaries require AVX; on CPUs without it
    mongod dies instantly with SIGILL (exit 132). mongodb-control never checked the mongod fork's
    exit status: it pinged the dead port for ~10 minutes, snapd restarted the service, and the cycle
    repeated forever — same limbo as when the data files are still MongoDB 3.x ("This version of
    MongoDB is too recent"). Now there is an AVX pre-flight and the fork/final-start exit codes are
    checked: if a COMPLETED FerretDB migration exists the snap switches to it; otherwise the
    MongoDB → FerretDB migration is (re)run — FerretDB is pure Go + SQLite and the 3.x reader uses
    the bundled MongoDB 3.2 tools, so neither needs AVX — with a 3-attempt counter so a persistently
    failing migration cannot ping-pong, and clear log guidance (snap run wekan.migrate).
    migration-control also skips the pointless mongod 7 probe when AVX is missing. Thanks to kiarn
    and xet7.

  • Snap: a migration that finished with a few per-item errors ("Avatars Errors") deleted the
    fully-copied FerretDB database and left the snap serving 502 Bad Gateway

    (#6466, snap-src/bin/migrate-mongo3-to-ferretdb.mjs,
    releases/migrate-mongodb-to-ferretdb.mjs, snap-src/bin/migration-control). Both importers
    treated ≥10 logged errors of ANY kind as failure — but per-item errors (one document that fails
    JSON parsing, one avatar that fails to extract) don't invalidate everything that DID copy. The
    failure path then discarded the whole migrated SQLite, set migrate=off, and "fell back" to
    MongoDB — impossible for a 6.09 upgrade, whose 3.x data files the bundled mongod 7 cannot open,
    producing the reported endless db-eval.mjs ping loop and 502. Per-item errors are now logged
    but non-fatal (only real failures — disk full, unreachable target/source — still fail), and a
    failed 3.x-source migration keeps the partial FerretDB SQLite plus its checkpoint and RESUMES on
    the next start instead of deleting hours of copied data. Thanks to Nissulya, S0QR2, lezioul,
    usrflo and xet7.

  • Snap/Bundle: FerretDB v1 pinned 250–400% CPU and boards took minutes to load after migration
    (#6467,
    #6468, snap-src/bin/wekan-control,
    releases/ferretdb/start-wekan.sh, and the wekan/FerretDB fork). Two sides. WeKan side: with
    FerretDB there is no oplog, so Meteor observes every query by POLLING — and its defaults re-run
    every observed query 50 ms after ANY write and at least every 10 s, which on an active board
    multiplies into hundreds of full queries per second; with FerretDB the snap and the bundle
    launcher now default to METEOR_POLLING_THROTTLE_MS=2000 / METEOR_POLLING_INTERVAL_MS=30000
    (overridable; own changes still appear instantly, other users' changes may take ~2 s longer).
    FerretDB side (fork v1.28): real filter pushdown so {boardId: X} uses the SQLite expression
    indexes instead of decoding the whole 53k-card collection per query, a connection pool cap of
    2×CPUs (was 100 — dozens of concurrent full scans thrashing the pure-Go SQLite mutexes were the
    reported 821k futex calls/30 s), and inserts no longer take the registry's global write lock.
    Thanks to anlx-sw, markusst1982 and xet7.

  • LDAP: group search filters were double-escaped and broke group filtering
    (#6460,
    PR #6469, packages/wekan-ldap/server/ldap.js).
    The group filter was post-processed with a global backslash-doubling replace, a leftover
    workaround from the ldapjs era that predates the proper escapedToHex hex escaping. It turned
    already-correct RFC 4515 escapes like \5c and \28 (an AD DN with an escaped comma, a group
    name with parentheses) into \\5c/\\28, which ldapts' strict filter parser rejects with
    "Invalid escaped hex character" — so group filtering, admin-status sync and role sync failed
    for exactly those directories. The redundant replace is removed; injection protection is
    unchanged (escapedToHex still hex-escapes the username). Thanks to ChristianMa97.

  • LDAP: enabling org/team sync made every LDAP login fail with 'forbidden'
    (#6461,
    PR #6470, packages/wekan-ldap/server/loginHandler.js,
    packages/wekan-ldap/server/sync.js). On the server, a nested Meteor.callAsync inherits the
    current method invocation's connection, so when the login handler called the
    setUserOrgsTeamsFromLdap method, its admin guard saw the client's login connection with no
    logged-in user yet and rejected the sync — and the unhandled rejection failed the whole login.
    (The nightly cron sync runs outside a method invocation, so it was unaffected — which is why
    this hid.) The login-time sync call now clears the inherited invocation context so it is a true
    server-to-server call, and org/team sync is additionally wrapped so an optional-enrichment
    failure is logged instead of blocking login. The same PR also throws the account-creation error
    from addLdapUser at the right point (it was previously used as a user object first), fixes
    external-avatar localization to use Avatars.writeAsync (the callback-style write no longer
    exists in ostrio:files 3.x, so localizing avatars silently did nothing), and adds */?
    wildcard support with unit tests to the LDAP_SYNC_ORGANIZATIONS_GROUPS /
    LDAP_SYNC_TEAMS_GROUPS allowlists. Thanks to ChristianMa97 and xet7.

Thanks to above GitHub users for their contributions and translators for their translations.

Weekly OSS security release digest.

The CVE patches and breaking changes that affected production tools this week. One email, every Sunday.

No spam, unsubscribe anytime.

Share this release

Track wekan

Get notified when new releases ship.

Sign up free

About wekan

The Open Source kanban, built with Meteor. GitHub issues/PRs are only for FLOSS Developers, not for support, support is at https://wekan.fi/commercial-support/ . New English strings for new features at imports/i18n/data/en.i18n.json . Non-English translations at https://app.transifex.com/wekan/wekan only.

All releases →

Related context

Related tools

Beta — feedback welcome: [email protected]