github CodesWhat/drydock v1.5.0-rc.25

pre-release3 hours ago

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 a groupAssignedSizeMap ref (populated by loadGroups() from the groups API response and reset to {} on error) that records each group's API-assigned member count. The flatten condition is now buckets[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 containerEnumerationFailed flag in Docker.watch() (app/watchers/providers/docker/Docker.ts) that suppresses the authoritative emitWatcherSnapshot when getContainers() 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 cause getContainers() to return a short or empty array without throwing — the containerEnumerationFailed guard does not fire, watch() emits an authoritative emitWatcherSnapshot with the degraded container list, and the controller's AgentClient.handleWatcherSnapshotEvent prunes 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 via inspect(), 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 optional diagnostics out-parameter and writes the number of containers dropped due to enrichment errors into diagnostics.enrichmentErrors; watch() creates and passes this object on every call, logs a Container enumeration degraded warning when the count is non-zero, and suppresses emitWatcherSnapshot whenever either containerEnumerationFailed is true or enumerationDiagnostics.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-applied and update-failed events after a compose recreate or on multi-agent deployments. When an update routed through the operation queue completed, the terminal lifecycle event (update-applied on success, update-failed on failure/rolled-back) was emitted from app/store/update-operation.ts:buildTerminalLifecycleEventBase with only containerName / containerId / operationId on the payload — no container object. Notification handlers in app/triggers/providers/Trigger.ts fell back to findContainerByBusinessId(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 a No container found for update-applied event => ignore debug log. This was the same class of race as #355 but for the operation-queue-driven path that bypasses UpdateLifecycleExecutor's direct emit. The fix persists a snapshot of the Container on the operation entry at enqueue time (app/updates/request-update.ts:createAcceptedContainerUpdateRequest) and buildTerminalLifecycleEventBase now forwards that snapshot on the terminal-lifecycle payload — both update-applied and update-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: sanitizeUpdateAppliedPayloadForAgentSse and sanitizeUpdateFailedPayloadForAgentSse in app/agent/api/event.ts include container when present (previously stripped to scalars only), and the controller's AgentClient.parseUpdateFailedEventPayload accepts and decorates an inbound container with the source agent name to mirror the existing applied-path behaviour. The snapshot is internal-only: a new toApiUpdateOperation helper in app/store/update-operation.ts strips it before serialising operations through GET /api/v1/update-operations/:id, GET /api/containers/:id/update-operations, and POST /api/operations/:id/cancel, so container labels and details.env are not exposed to API consumers.

Don't miss a new drydock release

NewReleases is sending notifications on new releases.