github LanternOps/breeze v0.111.0

latest releases: v0.113.0, v0.112.0, v0.111.1...
4 days ago

Breeze RMM v0.111.0 — work that waits for offline devices, a real customer record page, and a Stop button for running scripts.

Another large one: 201 commits, ~1,750 files and 35 migrations since v0.110.0. Seven multi-wave programs land together (offline work queue, script/automation cancellation, the organization record page + Service Management mode, configuration-policy inheritance, manual assets, manual network assets, the AI agent builder), plus the first thin slice of an opt-in AI Operator, the custom-field importer for migrations off another RMM, and a 62-row pre-release browser sweep that found and fixed five functional defects and four High paper cuts before the tag — with service install verified on real Windows and Linux hosts.

⚠️ No breaking API changes, but five defaults change on upgrade. Read them before you pull: work aimed at an offline device now queues instead of failing (#5243); "Remove device" now uninstalls the agent by default (#5034); network-monitor alerts now send notifications and fire automations (#5309); QuickBooks connections start pushing payments into QuickBooks at deploy (#4624); and on Linux/macOS breeze-agent service stop is now temporary while the watchdog is installed (#5296). Two migrations deliberately abort the deploy if they find pre-existing bad data — preflight SQL is shipped for both (#4991, #5028).

Summary

  • Offline work queue — patch jobs, automation run_script / execute_command actions and scan/rollback commands aimed at an offline device are persisted with a delivery deadline and claimed on the agent's next heartbeat instead of failing with device_offline. Queued work is visible and cancellable on the device page, and expires undelivered after its TTL. On by default (#5145, #5164, #5174, #5243, #5248).
  • Stop a running script or automation — per-row Stop and Force stop on execution history and execution detail, Cancel run on automation runs, honest status when a stop lands too late, and a real process-tree kill on the agent (#4967, #4971, #4990, #5030, #5033).
  • Organization record page + Service Management mode — one page per customer with Overview, Contacts, Sites, Devices, Tickets, Contracts & Billing and Activity tabs, cross-linked from tickets, invoices and quotes; plus a partner-level Service Management mode (native / external / off, Settings → Partner → Company) that gates the new Service Desk sidebar section and Billing (#5086, #5109, #5110, #5108).
  • Configuration-policy inheritance — a policy can inherit from a partner-wide baseline, with Override/Revert per feature tab, and every reader (agent delivery, automations, patch scheduling, event log, monitoring) now resolves the inherited settings instead of silently delivering nothing (#5099, #5122, #5125).
  • Devices without an agent — hand-entered manual assets as a third device class with warranty lookup and inventory reporting (#5242, #5255, #5253, #5261); manually added network assets including website/URL targets with an inline hand-off to an HTTP monitor (#5239, #5258, #5260); and the network device page with the unified device list now on by default (#5090).
  • AI agent builder — a four-step guided create flow with a server-evaluated review card, a 15-capability picker replacing the tool-allowlist textarea, a partner ceiling projection, and a per-agent list of scripts allowed to run unattended (#5054, #5057, #5062, #5064, #5065, #5067, #5089).
  • AI Operator (opt-in, off by default) — delegate a service-recovery incident from a device or alert to an operator task that keeps working after you close the browser, with a task detail page, a device feed tab and approval through the existing inbox (#5214, #5244, #5245, #5254, #5259, #5262, #5263, #5272).
  • Custom-field import from another RMM — a wizard for Datto, NinjaOne, ConnectWise Automate, N-central and CSV that imports both field definitions and device values, including the warranty target (#4991, #5028, #5043, #5044, #5055, #5060, #5061).
  • Device removal, finished — bulk restore, async bulk permanent delete with progress, an agent-uninstall state badge on removed devices, and an optional retention policy that purges removed devices after N days (#5034, #5035, #5042, #5047, #5053).
  • QuickBooks payment push — payments recorded in Breeze against an invoice already in QuickBooks are created there automatically, and removed when the Breeze payment is voided or fully refunded (#4624).

Added

  • Offline work queuedevice_commands.deliver_by plus a two-clock reaper and claim eligibility (#5145); a "Queued actions" section with cancel on the device page and uniform "Queued — device offline" copy (#5164); patch-policy offline_behavior (queue / skip, default queue) and a new queued patch result (#5174); a per-action "If the device is offline" Queue/Skip control on automation run_script / execute_command (#5243); an AI-tool live-connection hint and a docs sweep to the queued-on-reconnect promise (#5248).
  • Cancellation — the five closers that finalize a cancelling execution (#4967); a blocking, process-tree agent kill with honest reporting (#4971); the request side with graceSeconds and a cancel_script_execution AI tool at tier 3 (#4990); POST /automations/runs/:runId/cancel with devices_cancelled (#5030); the Stop / Force stop / Cancel run UI in all eight locales (#5033).
  • Organization record page — org-pinned shell and Overview (#5086); Contacts, Sites, Devices and Activity tabs (#5109); Tickets and Contracts & Billing tabs plus lockedOrgId cross-links (#5110); partner Service Management mode and the Service Desk sidebar section (#5108).
  • Config-policy inheritanceparent_policy_id, constraint triggers and an effective-links view (#5099); the create/detail/list UI, "Inheriting from" / "Inherited by N policies" banners and the tab payload matrix (#5122); the resolver sweep that makes every reader honour the parent, plus automation execution identity and the patch loader (#5125).
  • Manual assets — the manual_assets table with RLS and cascade registration (#5242); CRUD plus link/unlink routes (#5255); warranty subject XOR and inclusion in the inventory report (#5253); the third device class, add/edit modal and Manual segment in the device list (#5261).
  • Manual network assets — nullable discovered_assets.ip_address with a source provenance column and writer guards (#5239); the create route, form and entry points (#5258); website/URL targets, partner inventory export and docs (#5260).
  • AI agent builder — tool catalog, ceiling projection, wildcard policy merge and grant-only supervised keys (#5054); the capability picker (#5057); the four-step guided create flow with POST /ai/agents/preview (#5062); the edit drawer rendering the create flow's step components (#5067); scripts allowed to run unattended (#5065, #5089); truthful run_script outcomes and reachable stepper (#5064); the first Playwright spec for the flow (#5074).
  • AI Operator — baseline contracts, recipe cards and writer inventory (#5214); the thin-slice schema for tasks, operations and the task outbox (#5244); the EXPLAIN-as-breeze_app harness and lineage fixtures (#5245); task-scoped intent identity and dispatch claim on the release CAS (#5259); terminal outbox events and the outbox publisher (#5262); the task coordinator, continuation-run admission, submit_task_step and service-recovery verification (#5263); the read routes, task detail page and device feed (#5254); the task admission route and the Delegate to Operator action (#5272).
  • Custom fields — definition uniqueness, org-XOR-partner ownership and org-merge reconcile (#4991); one effective key namespace per device with no cross-axis shadowing (#5028); device_custom_field_values as a real table with projection and backfill (#5044); the definitions importer preview/commit (#5043); the device-value importer including the warranty target (#5055); the "Import from another RMM" wizard (#5060) and its migration guides (#5061).
  • Devices — bulk restore and async bulk permanent delete with a purge-run progress route (#5042); "Remove" asks about the agent and uninstalls by default (#5034); an agent-uninstall state badge on removed devices (#5035); a device_lifecycle config-policy feature and daily job that purges removed devices after N days, off by default (#5053); the Agent Version column coloured by its relation to the effective pin, with GET /agent-versions/effective (#5298).
  • Scripts — per-script acknowledgement of Strict security patterns, so a routine config script is no longer hard-blocked while Basic-level dangerous patterns stay unconditional (#5167).
  • Auth — Android Key Attestation and Play Integrity verifiers for L4 approver devices (#4969, #5165); the iOS Secure Enclave P-256 + App Attest client (#5163); an MFA policy change that would lock out already-enrolled users is refused with 409 mfa_policy_would_lock_out_users and an affected-user count (#5120).
  • Mobile — device detail now carries IP, logged-in user, OS version and open alert/ticket counts (#5144); the Systems tab issue count includes open fleet findings via GET /fleet/findings/counts (#5143); a fleet status strip on the Home empty state (#5142); new-ticket org selector and assignee picker (#5189).
  • i18n — a server-side i18n runtime with recipient-locale resolution for email and PDF (#3918) and a locale cookie with Astro.locals.locale and a correct <html lang> on first load (#5041).
  • PAM — Windows Event Log entries (IDs 1004–1008) plus audit.jsonl for the whole elevation lifecycle, carrying requester and approver identity (#5019).
  • Ops/ready and /health/ready as a real aggregate readiness contract, with API admission gated on the consumers the process role requires (#4007).

Improved

  • Performance — an (org_id, timestamp) index on device_process_samples for the per-org rollup (#5009), a partial audit_logs index for the abuse-signals sweep (#5018) and indexes for four recurring-job predicates that had no covering index (#5021), all built CONCURRENTLY; metric-anomaly runs no longer pile up on metric_rollups (#5297); collector command capture is bounded, shipped log batches are cleared and PIDs are counted cheaply (#5045).
  • Devices — dynamic device groups are re-evaluated off the request path on device change (#4630, #5015, #5039); manual maintenance actuation is gated and the lease persisted (#4921); maintenance mode is reachable per device and its toasts are pluralised (#5007); "show removed" adds rows and Compare stays visible past four selected (#5066); the network-asset modal stays mounted across the post-create refresh (#5271).
  • Remote access — Remote Tools and Overview pick up desktopAccess changes live (#5294); the viewer's watchdog-fallback WebRTC frame counter no longer sticks FPS at 0 (#5293); session teardown carries a reason so mid-session capture failures surface (#5303, #4929).
  • Agent packagingservice install on Linux and macOS now restarts the service on an enrolled host instead of leaving it stopped, and the watchdog recovers a stranded agent (#5296); the same class of fix for the Windows service install, which previously reported "running" when it was not and failed over an existing install (#5302); macOS package artifacts are cleaned up on uninstall (#5185); the desktop-helper launchd plist has one source (#5003).
  • Alerts — correlation-member moves are gated on the group with a group-first lock order (#5036); alert-axis org_id children are rewritten on a device org-move (#5005).
  • QuickBooks — mappings auto-sync after a decision and the sync states are named (#5094); a live sync state shows on the invoice card after Issue (#5093); Integrations stays on Accounting for nested QuickBooks tab hashes (#5092); the quick_support org is hidden from the mapping workbench (#5072).
  • CI / operability — expected DB guard trips now report as warnings with event codes rather than 500s: the software-inventory lock answers 503 and auto-retries, and a filter-preview timeout answers 422 (#5186).

Fixed

  • Maintenance windows — a device inside a window with Suppress scripts is now skipped by every script path (AI assistant run_script, automation run_script / execute_command, fleet-fix remediation, edition auto-migration), not just manual runs. The check is fail-closed and each path records a skip, not a failure; a window that cannot be evaluated still fails loudly (#4919, #5202).
  • Auth sessions — disabling your own MFA, registering or deleting a passkey, and confirming a phone number that replaces an active SMS factor all still revoke every other session but now replace the calling session in the same response instead of bouncing you to /login?reason=session-expired by your own action (#4934, #5008, #5038, #5194, #5198, #5203). An admin MFA reset now removes all factors including passkeys, transactionally, across all four reset paths (#4920, RMM-QA-166); recovery-code rotation can no longer save an empty set (#5037); unsafe MFA policy activation is refused (#4060, #5120); unresolved advanced-filter action scopes are closed and alert bulk actions use the full matched set rather than the capped 100-row preview (#5118).
  • Network-monitor alerts — ICMP/TCP/HTTP/DNS monitor alerts were written straight into alerts without publishing alert.triggered, so they only ever appeared in the inbox. They now go through the shared create+publish path (#5241, #5309). See the upgrade notes — this changes notification volume.
  • Tenancy / GDPRscript_categories.parent_id is erasure-safe and ownership-guarded (#4873, #5200); the intent org is preserved on ticket deletion (#4872, #4903); agent attribution survives a ticket org move (#4830, #4902); config_policy parent_policy_id is pinned in the erasure ledger and owner-move races are scoped to system (#5123, #5127); a single permanent delete cascades in a system context like bulk purge (#5047); the extensions boot tenancy sweep is restored (#4283, #4927); workspace SQLSTATE errors are unwrapped and direct comparisons guarded (#4245, #4928); precheckCommandExecution is gated on the deciding org (#5264, #5311).
  • Integrations — UniFi device/client list pagination is followed by offset (#5101, #5191), MAC comparisons are canonicalised in the cloud sync (#5102, #5195) and camelCase Integration API fields decode in deep telemetry (#5087, #5096); the Umbrella sync moved to the next-gen /reports/v2/activity endpoint (#4597, #5097); Windows service names with interior spaces are accepted and failed service commands surface (#5088, #5095).
  • QuickBooks — voiding a paid invoice no longer fails against QuickBooks (stale revision token, re-read and retried once) and voiding a QuickBooks-synced invoice with applied payments now fails fast with 409 INVOICE_HAS_PAYMENTS rather than desyncing (#5180, #5187); re-pushing an invoice after payment activity no longer fails with a stale SyncToken; a reauth outage no longer retires pending payment pushes; org erasure and org merge no longer discard a payment deletion Breeze still owes; invoices.paid_at is cleared whenever an invoice falls out of paid (#4542) — all in #4624, and the first two also fix v0.110.0.
  • Agent / remote desktop — the Winlogon GDI capture path honours the GetDIBits selection contract, fixing blank logon/UAC/secure-desktop capture (#5284, #5295); one reboot-deferral dialog per Windows session (#4940, #4993) and a re-prompt with a postponement WARN (#4941, #4997).
  • Self-hosting — with BINARY_SOURCE=github and AGENT_AUTO_PROMOTE=false, the download route now serves the pinned version, so pinned devices no longer sit in "Updating" forever on a checksum mismatch (#5159, #5196).
  • AI — a lost transitionIntent CAS is handled at every aiAgentSdk call site (#5232, #5310); an org-only agent with no partner baseline is warned about (#4170, #5014); the execute_command approval headline and impact are command-type aware (#5173, #5178); an approved-by-worker result is no longer rendered as a FAILED tool result (#5107, #5121); the AI kill-switch actor resolves to a name (#4931, #4994).
  • Web — the Processes tab no longer reads a 503 as "0 processes / No Data" (#4935, #4996); ScriptExecutionModal's auto-close timer is cancelled on unmount (#5270, #5308); device-removal browser-walk defects — uninstall badge never rendered, stale filter after restore, purge confirm parity (#5023, #5058); collapsed-sidebar rail icons are centred (#5068); quote-editor paper cuts and multi-run cell alignment with theme fonts (#4438, #4937, #5006, #5013).
  • Portal — disabled visibility pages redirect instead of erroring (#4932, #4995) and ticket-page gate 403s route through the shared home redirect (#5029).
  • Mobile — chat render paper cuts (streaming markdown, expandable errors, honest tool labels) (#5170, #5176); findings 404s degrade silently and the approval empty state stops flashing (#5172, #5177); ticket keyboard dismiss, toast clearance and US spelling (#5171, #5175); runaway timer warning, MFA auto-submit, approval header and org offline count (#5115, #5138); metrics key mismatch, keyboard persistence, missing org on device rows and 0m timesheets (#5104, #5113); ticket photo upload FormData part and the library permission prompt (#5103, #5112); toast/composer overlap and Systems org-filter hero (#5105, #5114).
  • API — device diagnose no longer 500s on bigint metric columns (#4974, #4992); :id is validated as a UUID on fleet findings routes (#5179, #5184); a patch job result summary accepts a nullable patch_id; Sentry tags in accountingConnectionService / quickbooksProvider / accountingPaymentPull use allowlisted snake_case keys (#5126, #5192, #5193, #5201).
  • Plus ~35 further fixes across CI, tests, i18n copy and internal tooling.

Security

Self-hosters are encouraged to upgrade. No advisories are pending for this range (draft-advisory pre-flight run and clean).

Security — hardening

  • Delegated API keys with site restrictions could bypass them on custom-field writes and dev-push binary distribution. Both are now denied (#5182, RMM-QA-162).
  • Alert summaries ignored read permission and site scope — a custom role without alerts:read could read alert summaries, and a site-restricted user saw organization-wide alert counts. Both closed; some users will correctly see less data than before (#5070).
  • Software and SentinelOne summaries were org-wide for site-restricted users, and organization status / integration metadata leaked across the RLS boundary. SentinelOne status now requires devices:read (#5071, RMM-QA-221).
  • Partner-wide config rows were reachable from the wrong axis, and unreachable from the right one. The missing FOR SELECT-only partner-wide branch was added to the identity-contracts, notification, software-security, automation/alert-rule, AI and custom-field-definition tables — five migrations plus their RLS-coverage registrations (#4942#4963, #4970, #4944, #4949#4958, #4998, #4999, #5000, #5010, #5012, #5197).
  • custom_field_definitions could shadow across the org/partner axis, letting one axis silently override the other's value for the same device key; a trigger now refuses it (#5028) and definition uniqueness plus org-XOR-partner ownership is enforced at the schema level (#4991).
  • script_categories.parent_id blocked erasure and was not ownership-guarded (#4873, #5200); getConnectionById is anchored on the caller's access axis (#4959, #5011).
  • AI agents — an organization-owned agent can no longer grant itself a supervisedActionKeys entry it does not already hold (422 supervised_keys_grant_only); keys reach org rows only through the four-eyes graduation grant. actAssets.scriptIds is validated on every write (#5054, #5089).
  • PAM elevation is now auditable off-box on Windows — Event Log IDs 1004–1008 plus audit.jsonl, with requester and approver identity (#4913, #5019).

Self-Hosting / Upgrade Notes

No breaking API changes. No new required environment variables. Five defaults change on upgrade — read "Behaviour changes" below before you pull.

Upgrade command — bump BREEZE_VERSION in /opt/breeze/.env, then:

docker compose pull api web portal && docker compose up -d binaries-init api web portal

(portal is a separate container — include it. If you have opted into the worker split, add worker. Building from source: pnpm install first.)

⚠️ Known upgrade blocker in 0.111.0 (found during the hosted rollout): 2026-10-14-100100-discovered-assets-manual-source.sql crash-loops the API on any database whose discovered_assets rows span more than one organization or partner. Its set-based backfill fires the partner-export consistency triggers row by row, which trips the lock-hierarchy guards (partner export lock hierarchy violation / organization locks must be acquired in ascending UUID order); the file rolls back and boot fails until it succeeds. Hosted US hit this for ~20 minutes; EU and single-tenant installs are unaffected. Before you pull 0.111.0, run this once against your database (it is idempotent and safe on 0.110.0; the migration's own backfill then matches zero rows), or, better, install v0.111.1 instead, which ships this as a migration that sorts ahead of the failing one:

BEGIN;
SELECT set_config('breeze.scope', 'system', true);
DO $$ BEGIN CREATE TYPE public.discovered_asset_source AS ENUM ('scan','unifi','manual'); EXCEPTION WHEN duplicate_object THEN NULL; END $$;
ALTER TABLE public.discovered_assets ADD COLUMN IF NOT EXISTS source public.discovered_asset_source;
ALTER TABLE public.discovered_assets ADD COLUMN IF NOT EXISTS url text;
SELECT public.breeze_partner_export_lock_partners_shared(ARRAY(
  SELECT DISTINCT o.partner_id FROM public.discovered_assets d JOIN public.organizations o ON o.id = d.org_id
   WHERE d.source IS NULL AND o.partner_id IS NOT NULL ORDER BY 1));
SELECT public.breeze_partner_export_lock_orgs_exclusive(ARRAY(
  SELECT DISTINCT d.org_id FROM public.discovered_assets d WHERE d.source IS NULL AND d.org_id IS NOT NULL ORDER BY 1));
UPDATE public.discovered_assets a SET source = 'unifi'
 WHERE a.source IS NULL AND (a.detected_type_source = 'unifi_controller'
    OR EXISTS (SELECT 1 FROM public.unifi_devices u WHERE u.discovered_asset_id = a.id));
UPDATE public.discovered_assets SET source = 'scan' WHERE source IS NULL;
COMMIT;

If you already pulled and the API is looping on that file: run the block above, then restart the API; the remaining migrations complete. After the API is healthy, run docker compose up -d web portal — compose leaves them in Created while the API is unhealthy and does not start them on its own.

Database — 35 idempotent migrations (plus 2 preflight files). Take a snapshot first. All auto-apply on API boot via autoMigrate (unless AUTO_MIGRATE=false).

Two of them will deliberately abort your deploy if they find pre-existing bad data. This is intentional — the alternative is silently picking a winner among rows that must be unique:

  • 2026-10-10-100300-custom-field-definition-integrity.sql (#4991) aborts on duplicate or ownerless custom_field_definitions rows. Preflight: apps/api/migrations/preflight/2026-10-10-100300-custom-field-definition-integrity-preflight.sql.
  • 2026-10-11-141000-custom-field-no-cross-axis-shadowing.sql (#5028) aborts on an existing org/partner cross-axis key collision. Preflight: apps/api/migrations/preflight/2026-10-11-141000-custom-field-shadowing-preflight.sql.

Run both preflights against your DB before you pull, and reconcile anything they report. Hosted EU/US are clean.

Backfills that touch existing rows run under breeze.scope=system and log their row counts as Postgres WARNINGs. Three to read after the first boot:

  • device_custom_field_values backfill: copied N value(s) out of devices.custom_fields, minted N definition(s) for previously undefined keys and N stored key(s) do not match the enforced key pattern and stay in the jsonb projection only (2026-10-11-160000-device-custom-field-values.sql, #5044) — proportional to devices × custom-field keys. Camel-case keys written before the pattern was enforced are deliberately preserved in the jsonb only.
  • backfilled N discovered_assets rows to source=unifi / source=scan (2026-10-14-100100-discovered-assets-manual-source.sql, #5239) — an unbounded UPDATE over every discovered_assets row with a null source. Sized by your discovery inventory, not your device count; on a large scanned estate this is the longest statement in the set.
  • 2026-10-11-170000-partner-admin-force-mfa-reconcile.sql (#4491) stamps force_mfa = true on every existing system Partner Admin role. Enforcement is off this release — see behaviour changes.

The three perf(db) index migrations (device_process_samples (org_id, timestamp), the abuse-sweep partial audit_logs index, four recurring-job indexes — #5009, #5018, #5021) build CONCURRENTLY through the no-transaction lane, so writes are not blocked; an interrupted build leaves an INVALID index the migration reports loudly on the next boot (recovery: DROP INDEX CONCURRENTLY <name> and restart). 2026-10-14-100000-manual-assets.sql takes a brief SHARE lock on discovered_assets while it creates the new table's FKs.

Migration audit (35 files, reviewed statement-by-statement before the tag). No CREATE ROLE, ALTER … OWNER TO, CREATE EXTENSION or schema-level GRANT/REVOKE; every GRANT is table-, sequence-, view- or function-level and issued by the object's own owner, so a non-superuser migrating role (DigitalOcean doadmin, RDS master) is sufficient. All twelve files that write rows elect breeze.scope = system before their first statement, and two also abort if that elevation did not take. Every CREATE OR REPLACE FUNCTION in the set was diffed against its predecessor: byte-identical plus the intended change. Expected boot cost is dominated by index builds and one hot-table constraint:

  • 2026-10-13-100000-device-commands-deliver-by.sql adds a foreign key and a partial index on device_commands without NOT VALID / CONCURRENTLY, so it holds an exclusive lock on the agent command path for the duration of one sequential scan. At hosted sizes (tens of thousands of rows) that is seconds; on an estate with millions of retained commands, pre-build them by hand before pulling (ADD CONSTRAINT device_commands_submitted_org_id_fkey … NOT VALID then VALIDATE CONSTRAINT, and CREATE INDEX CONCURRENTLY idx_device_commands_deliver_by … with that exact name) so the migration's guards no-op at boot.
  • The six CONCURRENTLY indexes are the long pole on a large audit_logs / device_process_samples: minutes on a small managed instance, zero if they already exist by name (the API does not serve until they finish).
  • 2026-10-11-160000-device-lifecycle-feature-and-decommissioned-at.sql builds one non-concurrent partial index on devices and 2026-10-14-100000-manual-assets.sql one on discovered_assets; both briefly block writes to those tables. 2026-10-14-100100-discovered-assets-manual-source.sql also takes SET NOT NULL plus two CHECK validations under an exclusive lock on discovered_assets.
  • 2026-10-14-100200-device-warranty-manual-asset-subject.sql replaces three SECURITY DEFINER export trigger functions; after boot confirm SELECT proname, proacl FROM pg_proc WHERE proname LIKE 'breeze_partner_export_device_child_%' still shows no =X/ grant to PUBLIC (hosted baseline before the deploy: owner-only).

New optional environment variables (missing = the documented default; none of these fail boot; map any you set in the api service's environment: block of your compose, not just .env):

  • DEVICE_COMMAND_OFFLINE_QUEUE_ENABLEDdefaults to ON (#5243). Set false to restore the pre-#5128 hard device_offline rejection for patch jobs, automation run_script / execute_command and scan/rollback commands. This escape hatch is removed in a later release (#5249). Companion TTL knobs, all documented in .env.example: DEVICE_COMMAND_QUEUE_TTL_HOURS (168), DEVICE_COMMAND_QUEUE_SHORT_TTL_HOURS (24), DEVICE_COMMAND_QUEUE_POWER_STATE_TTL_HOURS (24).
  • MFA_FORCE_FOR_PARTNER_ADMINnow defaults to false (#5307). Set true to enforce forced MFA for Partner Admins now; otherwise the reconcile migration's force_mfa = true is recorded but not acted on. Enforcement returns to default-on once the notification-period rollout ships (#5306).
  • AI_OPERATOR_TASKS_ENABLEDdefault false. Gates AI Operator task admission and continuation-run admission (#5263, #5272).
  • AI_OPERATOR_RECIPE_SERVICE_RECOVERY_ENABLEDdefault false. Gates the one shipped recipe (service recovery). Both flags off means the whole AI Operator subsystem is inert, including its two background workers.
  • PLAY_INTEGRITY_SERVICE_ACCOUNT — Google service-account JSON (raw or base64) for decoding Play Integrity verdicts on Android approver-device registration (#4969). Absent = Key-Attestation-only. Format is validated, so a mangled paste stops the boot rather than silently reporting "not configured" forever.
  • READINESS_CACHE_TTL_MS (5000), READINESS_PROBE_TIMEOUT_MS (3000) — tuning for the new readiness contract (#4007).
  • OFFLINE_DETECTOR_WORKER_CONCURRENCY (5) — offline-sweep worker concurrency (#4007).

Readiness vs liveness (#4007). /health remains liveness and is unchanged. /ready and /health/ready now serve a real aggregate readiness contract, and API admission is gated on the live consumers the process role requires. If your load balancer or compose healthcheck should hold traffic until Redis and Postgres consumers are actually up, point it at /ready; /health will answer 200 before that.

New workers and routes. Two new background workers ship with AI Operator — aiOperatorTaskWorker (task coordinator, 15 s tick) and aiOperatorTaskOutboxPublisher (5 s tick) — both inert while the two AI Operator flags are off. No other new BullMQ queue. New routes: POST|GET /api/v1/ai/operator/tasks and /:id; GET /ai/agents/tool-catalog, GET /ai/agents/ceiling, POST /ai/agents/preview; POST /automations/runs/:runId/cancel; POST /custom-fields/import/preview, POST /custom-fields/import, POST /devices/custom-fields/import/preview, POST /devices/custom-fields/import (MFA + devices:write; API keys rejected; capped at 1,000 rows / 5,000 values per batch); GET|POST|PATCH|DELETE /devices/manual plus link/unlink; POST /devices/network; GET /devices/removal-config; POST /devices/bulk/permanent-delete (202 + jobId), GET /devices/bulk/purge-runs/:jobId, POST /devices/bulk/restore; GET /configuration-policies/eligible-parents; GET /agent-versions/effective; GET /fleet/findings/counts; GET /discovery/assets/:id gains siteName and suggestedBridgeDeviceId; GET /roles gains activeUserCount. Agent download routes accept an optional ?version=.

Behaviour changes

  • ⚠️ QuickBooks starts pushing payments into every connected realm at deploy (#4624). The new accounting_connections.push_payments column defaults to true, so a realm that is connected and in push_mode = auto begins creating QuickBooks Payments as soon as the API restarts — no operator action, no per-realm opt-in. If you are not ready for Breeze to write into a realm's books, set it to false first (Integrations → QuickBooks → "Push payments to QuickBooks"). Only payments recorded after the switch became active are pushed: the migration stamps every existing connection's push_payments_since with the deploy time, and toggling the switch off and on re-stamps it, so a pause never later flushes a backlog. There is no supported way to push a payment recorded before that horizon. A payment deletion propagates regardless of both push_mode and push_payments — once Breeze created a Payment in QuickBooks it owns the removal, so switching the feature off cannot strand money in the books. A failing push gives up after 100 attempts (~5 hours in practice) with a message naming the reason; "Push to QuickBooks" clears the counter. The reconcile gate widened from pull_payments to pull_payments OR push_payments, so a pull-off/push-on realm now runs the CDC pass over Breeze's own payments only, counting every QuickBooks-origin line as skipped_pull_disabled and holding the CDC cursor so nothing is permanently skipped when pull is turned back on.
  • Work aimed at an offline device queues instead of failing (#5243). Patch jobs, automation run_script / execute_command actions and scan/rollback commands are persisted with a delivery deadline (7 days standard; 24 hours for inventory and power-state work) and claimed on the agent's next successful heartbeat. A waiting step reads "Queued — device offline" instead of Failed, so a nightly automation over sleeping laptops no longer shows a wall of red — and correspondingly, work you previously saw fail fast now sits pending for up to a week. Queued rows are visible and cancellable on the device page. Existing automations have no stored whenOffline value and therefore queue; set the new per-action control to Skip for a step that is only meaningful against a live device. Manual Run Script and software installs already queued and are unaffected.
  • "Remove device" now uninstalls the agent by default (#5034). The web flow asks about the agent and defaults to uninstalling it; previously a wiring bug meant the uninstall was effectively never sent and agents were left running on removed devices. A permanent delete now refuses with 409 UNINSTALL_PENDING until the uninstall has been collected (#5042), and the single permanent-delete response no longer carries agentUninstallSent / warning.
  • ⚠️ Network-monitor alerts now notify, escalate and automate (#5241, #5309). ICMP/TCP/HTTP/DNS monitor alerts previously landed in the Alerts inbox only. They now publish alert.triggered, so they send email/Teams/Slack/webhook/PagerDuty/SMS/Pushover notifications, start escalation policies, fire alert.triggered automations and receive AI verdicts. Expect a step change in notification and automation volume on upgrade if you have network monitors with alert rules — review those rules, channels and escalation policies before deploying. The 5-minute per-rule cooldown and the source = network_monitor dedupe still apply, and recovery still auto-resolves. The event carries source: 'network_monitor', monitorId, alertRuleId, monitorType and target, so an automation filter can be narrowed; ruleId is null for these alerts.
  • ⚠️ On Linux and macOS, breeze-agent service stop is now temporary while the watchdog is installed (#5296). The watchdog restarts the agent after watchdog.standby_grace (default ~2 minutes). For a durable stop, stop or uninstall the watchdog too. Related: service install now restarts the service on an already-enrolled host rather than leaving it stopped — the old behaviour stranded a host Offline until someone ran systemctl start by hand. The Windows equivalent is fixed in #5302, which also fixes an install over an existing service (it previously failed with Access is denied and reported "running" when it was not). Both paths were verified on real hosts (Server 2022 and Ubuntu 22.04) before the tag. Known Linux-only wrinkle: systemctl stop breeze-agent runs the full TimeoutStopSec=30s and ends in 9/KILL, so each install takes ~32 s and the unit is briefly failed — pre-existing, tracked as #5334.
  • Forced MFA for Partner Admins is reconciled but NOT enforced (#4491, #5307). 2026-10-11-170000-partner-admin-force-mfa-reconcile.sql marks every existing system Partner Admin role force_mfa = true on every install, reconciling installs that predate RMM-QA-164 — but MFA_FORCE_FOR_PARTNER_ADMIN now defaults to false, because the migration alone would otherwise lock existing Partner Admins into enrolment at upgrade time with no warning. Set it to true to enforce now. A notification-period rollout (grace window, banner, deadline) ships next release as #5306, at which point enforcement returns to default-on. Neither hosted region pins the variable.
  • Maintenance windows suppress every script path (#4919, #5202). A device inside a window with Suppress scripts is now skipped by the AI assistant's run_script, automation run_script / execute_command, fleet-fix remediation and edition auto-migration — not just manual Run Script. Scripts that used to run during a window will now be skipped (recorded as a skip, not a failure: the automation run stays green and trailing actions still run; fleet-fix targets get a maintenance_window skip reason). A window that cannot be evaluated is deliberately the opposite and keeps the ordinary failure treatment, so an outage of the maintenance config can never render as a fleet of green runs.
  • Script cancellation kills the whole process tree (#4971). Cancel and timeout now terminate descendants on Windows and Unix. A script that intentionally detaches a child process will now have that child killed too — worth a canary ring if you rely on that pattern.
  • Metric-anomaly detection: lookback shortened 30 → 15 minutes with a 90 s statement timeout (#5283, #5297). This stops overlapping runs piling up lock waits on metric_rollups (a real incident). The trade-off: a skipped tick no longer self-heals — a gap needs a manual backfill.
  • Unified device list is on by default (#5090). PUBLIC_ENABLE_NETWORK_DEVICES_IN_LIST now defaults to true. It is a build-time PUBLIC_ variable baked into the web image, so the published GHCR web image ships with the unified list (All / Agent / Network / Manual segments) on. Nothing to change in .env or compose. Self-hosters who build the web image themselves can pass false to keep the agent-only list.
  • Service Management mode is a PARTNER setting, not per-organization — Settings → Partner → Company (Modules card), via PATCH /partners/me (#5108). native is the default and existing partners are unchanged. off hides the Service Desk and Billing surfaces (including the org record's Tickets and Contracts & Billing tabs) and refuses ticket creation everywhere with a 409 (UI, portal, email-to-ticket, AI tool). external has no UI yet — it requires a bound partner-wide psa_connections row and is reachable only via the API in this release.
  • Config-policy inheritance changes what a "linked" policy delivers (#5099, #5125). Previously a policy marked as inheriting could silently deliver nothing to its devices; every reader now resolves the real inherited settings. If you had such a policy, devices under it will start receiving configuration they were not receiving before.
  • Cross-org device moves may now drop custom-field values not visible in the target org (#5044) — previously kept silently, now dropped and logged.
  • API contract tightening — a fully cancelled automation run reports cancelled, not failed (#5030); POST /scripts/executions/:id/cancel gains graceSeconds / cancelling and answers 409 on a terminal state (#4990); a colliding custom-field create answers 409 instead of 500 (#5028); voiding a QuickBooks-synced invoice with applied payments answers 409 INVOICE_HAS_PAYMENTS (#5187); passkey register/delete and phone confirm can now answer 409 (concurrent auth issuance / factor set changed — nothing written) and 428 (client auth binding must rotate first) (#5038, #5198); an MFA policy change that would lock out enrolled users answers 409 mfa_policy_would_lock_out_users (#5120); an org-owned AI agent adding an unheld supervisedActionKeys entry answers 422 supervised_keys_grant_only, and bad actAssets.scriptIds answer 422 invalid_script_ids (#5054, #5089); the software-inventory lock answers 503 (auto-retried) and a filter-preview timeout answers 422, both previously 500 (#5186).
  • Still off by default: BREEZE_AI_AGENTS_ENABLED gates the whole AI-agent subsystem including the new builder; AI_OPERATOR_TASKS_ENABLED and AI_OPERATOR_RECIPE_SERVICE_RECOVERY_ENABLED gate AI Operator; reboot deferral is still per-policy opt-in; the removed-device purge retention policy (#5053) ships off, bounded to 1–3,650 days when you enable it.

Not verified on real hardware before the tag (each needs an environment we do not have in the lab, and each is flagged in the pre-release sweep): the iOS Secure Enclave / App Attest client (#5163, TestFlight round), Winlogon logged-out console capture (#5295), UniFi dedupe (#5195, #5096), Umbrella v2 (#5097), and the QuickBooks payment-push rows that need a live sandbox connection — though the QuickBooks sandbox walkthrough itself was run on 2026-09-06 and found and fixed four defects on the branch.

Privileged statements were audited against a non-superuser role: the range creates SECURITY DEFINER trigger functions owned by the migrating role and nothing else privileged — no CREATE ROLE, ALTER … OWNER TO, CREATE EXTENSION or schema GRANTs.

Full Changelog: v0.110.0...v0.111.0

What's Changed

New Contributors

Full Changelog: v0.110.0...v0.111.0

Don't miss a new breeze release

NewReleases is sending notifications on new releases.