v1.5.0-rc.25
[1.5.0-rc.25] — 2026-05-21
Fixed
-
#371 — Containers "Group By Stack" view no longer dissolves a multi-container stack into "Ungrouped" while its last container is mid-update. The flatten rule in
groupedContainers(ui/src/views/ContainersView.vue) previously keyed off the transient live container count (buckets[key].length === 1). During a docker recreate a 2-container stack momentarily shows only 1 live container (old removed, new not yet added), so the rule fired and dropped the stack header. The fix adds agroupAssignedSizeMapref (populated byloadGroups()from the groups API response and reset to{}on error) that records each group's API-assigned member count. The flatten condition is nowbuckets[key].length === 1 && groupAssignedSizeMap.value[key] === 1— a strict equality check so stacks whose assigned size is > 1 or transiently absent from the API response are never flattened mid-update. Genuine single-container stacks (assigned size exactly 1) are still flattened as before (GitHub Discussion #179). -
#386 — Agents intermittently showing 0 running containers in the controller UI — a recurrence of #362 that the rc.20 fix did not fully close. The rc.20 guard introduced a
containerEnumerationFailedflag inDocker.watch()(app/watchers/providers/docker/Docker.ts) that suppresses the authoritativeemitWatcherSnapshotwhengetContainers()itself throws. However,getContainers()does not throw on per-container enrichment failures:addImageDetailsToContainer()is called for each watched container, and any container whose enrichment throws is caught (.catch(error => return error)) and then silently filtered out by.filter(result => !(result instanceof Error) && result != null). A transient docker / socket-proxy hiccup during image inspect can therefore causegetContainers()to return a short or empty array without throwing — thecontainerEnumerationFailedguard does not fire,watch()emits an authoritativeemitWatcherSnapshotwith the degraded container list, and the controller'sAgentClient.handleWatcherSnapshotEventprunes every container not in that list, wiping the agent's view. The agent's own store is preserved because its local prune re-confirms each container viainspect(), which is why the agent kept reporting its containers and a restart's handshake re-synced the controller. The fix extends the snapshot-suppression in two steps:getContainers()now accepts an optionaldiagnosticsout-parameter and writes the number of containers dropped due to enrichment errors intodiagnostics.enrichmentErrors;watch()creates and passes this object on every call, logs aContainer enumeration degradedwarning when the count is non-zero, and suppressesemitWatcherSnapshotwhenever eithercontainerEnumerationFailedis true orenumerationDiagnostics.enrichmentErrors > 0. Per-container reports still emit as before; only the authoritative controller-side prune is deferred until a fully clean watch cycle. -
#385 — Telegram, Pushover, and other notification triggers no longer silently swallow
update-appliedandupdate-failedevents after a compose recreate or on multi-agent deployments. When an update routed through the operation queue completed, the terminal lifecycle event (update-appliedon success,update-failedon failure/rolled-back) was emitted fromapp/store/update-operation.ts:buildTerminalLifecycleEventBasewith onlycontainerName/containerId/operationIdon the payload — nocontainerobject. Notification handlers inapp/triggers/providers/Trigger.tsfell back tofindContainerByBusinessId(containerName), which missed during the ~8 s window between the old container being removed and the new one being re-watched after a compose recreate; the handler then dropped the event with aNo container found for update-applied event => ignoredebug log. This was the same class of race as #355 but for the operation-queue-driven path that bypassesUpdateLifecycleExecutor's direct emit. The fix persists a snapshot of theContaineron the operation entry at enqueue time (app/updates/request-update.ts:createAcceptedContainerUpdateRequest) andbuildTerminalLifecycleEventBasenow forwards that snapshot on the terminal-lifecycle payload — bothupdate-appliedandupdate-failed, closing the race for compose successes and failures alike. The agent SSE wire was also extended to forward the container snapshot end-to-end so multi-agent deployments get the same fix:sanitizeUpdateAppliedPayloadForAgentSseandsanitizeUpdateFailedPayloadForAgentSseinapp/agent/api/event.tsincludecontainerwhen present (previously stripped to scalars only), and the controller'sAgentClient.parseUpdateFailedEventPayloadaccepts and decorates an inbound container with the sourceagentname to mirror the existing applied-path behaviour. The snapshot is internal-only: a newtoApiUpdateOperationhelper inapp/store/update-operation.tsstrips it before serialising operations throughGET /api/v1/update-operations/:id,GET /api/containers/:id/update-operations, andPOST /api/operations/:id/cancel, so container labels anddetails.envare not exposed to API consumers.