v1.5.0-rc.27
Full Changelog: v1.5.0-rc.26...v1.5.0-rc.27
[1.5.0-rc.27] — 2026-05-24
Fixed
-
#289 — Agent-hosted container updates no longer leave an orphaned queued operation row on the controller that the 30-minute TTL sweep force-fails into a misleading "update failed" Pushover/Telegram notification long after the update actually succeeded. A user running drydock in a controller + agent topology reported on rc.25 that an "Update All" of Tautulli on two hosts produced the success notification only for the controller-host container; the agent-host container's success notification was missing and, ~30 minutes later, a second Pushover arrived saying
[mediavault] Container Tautulli update failed — Marked failed after exceeding active update TTL (1800000ms) while queued.even though the update had in fact succeeded on the agent. Cause: when the controller queues a container update viacreateAcceptedContainerUpdateRequest(app/updates/request-update.ts) it mints a controller-sideoperationIdand inserts aqueuedrow; the dispatcher then callsentry.trigger.trigger(entry.container, { operationId }). For containers hosted on an agent the trigger isAgentTrigger, whosetrigger(container)previously accepted only the container and discarded theruntimeContext.AgentClient.runRemoteTriggerposted{id, name}to the agent without the operationId, so the agent's/api/triggers/:type/:nameendpoint calledrequestContainerUpdatewith no operationId and minted its own row; the agent'sdd:update-applied/dd:update-operation-changedevents then arrived back at the controller carrying the agent-side id, which the controller routed throughtoAgentScopedIdinto a third, agent-scoped row (agent-<name>-<remote-id>). The original controller-side queued row was therefore never touched, sat queued past theUPDATE_OPERATION_ACTIVE_TTL_MSdeadline inapp/store/update-operation.ts:295-300, and was force-failed by the TTL sweep — which fired the misleading "failed" notification with the row's still-valid container snapshot (hence the correct[mediavault]agent prefix). The fix threads the controller'soperationIdend-to-end so a single row is the source of truth for the whole lifecycle:AgentTrigger.trigger/triggerBatchnow accept and forwardruntimeContext;AgentClient.runRemoteTrigger/runRemoteTriggerBatchextract per-container operationIds via the existinggetRequestedOperationIdhelper and include them in the agent payload ({id, name, operationId}for single triggers;{...container, operationId}per entry for batches); the agent-side controllerrunTriggeraccepts anoperationIdin the request body (validated bytriggerRequestBodySchema) and threads it intorequestContainerUpdate; the agent-side batch endpoint extracts per-container operationIds into an{operationIds}runtimeContext before forwarding to the local trigger;EnqueueContainerUpdateOptionsgains anoperationIdfield honored bycreateAcceptedContainerUpdateRequest(single-container batches only; multi-container batches still mint per-container UUIDs); and a newAgentClient.resolveAgentOperationIdhelper checks the controller's operation store for an existing row at the raw (unscoped) id and reuses it when found — falling back to thetoAgentScopedIdform only when the agent does not echo a known controller id, preserving backwards compatibility with older agents. The controller-side queued row therefore transitions directly toin-progressandsucceeded/failedfrom the agent's lifecycle events, no parallel agent-scoped row is created, the TTL sweep has nothing stale to fail, and the spurious "update failed" notification disappears. -
#289 — Update-applied and update-failed notification triggers (Pushover, Telegram, etc.) and UI success toasts no longer silently drop for containers running on a connected agent. A user running drydock in a controller + agent topology reported on rc.25 that an "Update All" across two hosts produced the success toast and Pushover notification only for the container on the controller host, never for the same-name container on the agent host. Cause: when the agent finishes an update it sends a
dd:update-appliedSSE payload to the controller carrying a fullcontainersnapshot. The controller'sAgentClient.handleEventroutes this throughmaybeMarkAgentOperationSucceededFromAppliedPayload→markAgentOperationTerminal→ensureAgentOperationForTerminal→updateOperationStore.insertOperation+markOperationTerminal, butbuildAgentOperationBaseinapp/agent/AgentClient.tsconstructed the inserted row from{id, kind, containerName, containerId, newContainerId}only — the container snapshot was dropped on the floor. WhenmarkOperationTerminalthen firedemitTerminalLifecycleEvent(app/store/update-operation.ts), the resultingemitContainerUpdateApplied/emitContainerUpdateFailedpayload built bybuildTerminalLifecycleEventBaselackedcontainer. The notification handlerhandleContainerUpdateAppliedEvent(app/triggers/providers/Trigger.ts) then fell back tofindContainerByBusinessId(containerName), which compares the agent's barecontainerName(e.g.tautulli) against the controller-sidefullName(e.g.mediavault_docker_tautulli) and silently dropped — the same class offindContainerByBusinessIdmiss as #385 but on the agent-scoped operation path that #385 did not cover. The fix threads the agent's container snapshot through every level of the agent-scoped operation pipeline —buildAgentOperationBase,ensureAgentOperationForTerminal,markAgentOperationTerminal,maybeMarkAgentOperationSucceededFromAppliedPayload, andmaybeMarkAgentOperationFailedFromFailedPayload— stampingagent: this.nameso the controller's view of the container is consistent. Thedd:update-operation-changed-before-dd:update-appliedrace is handled by patching the container snapshot onto the existing active row viaupdateOperationbefore the terminal emit runs (only when the existing row lacks a container, never overwriting an existing snapshot).containeris added toMutableUpdateOperationFieldsinapp/store/update-operation.tsso terminal and active patches accept it. The store's terminal-lifecycle emit therefore naturally carries the agent's container intoemitContainerUpdateApplied/emitContainerUpdateFailed, thepayloadContainershortcut in the trigger handler succeeds, and both the notification trigger and the SSE toast fire end-to-end on the controller for agent-originated updates.