github CodesWhat/drydock v1.5.0-rc.27

pre-release7 hours ago

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 via createAcceptedContainerUpdateRequest (app/updates/request-update.ts) it mints a controller-side operationId and inserts a queued row; the dispatcher then calls entry.trigger.trigger(entry.container, { operationId }). For containers hosted on an agent the trigger is AgentTrigger, whose trigger(container) previously accepted only the container and discarded the runtimeContext. AgentClient.runRemoteTrigger posted {id, name} to the agent without the operationId, so the agent's /api/triggers/:type/:name endpoint called requestContainerUpdate with no operationId and minted its own row; the agent's dd:update-applied / dd:update-operation-changed events then arrived back at the controller carrying the agent-side id, which the controller routed through toAgentScopedId into a third, agent-scoped row (agent-<name>-<remote-id>). The original controller-side queued row was therefore never touched, sat queued past the UPDATE_OPERATION_ACTIVE_TTL_MS deadline in app/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's operationId end-to-end so a single row is the source of truth for the whole lifecycle: AgentTrigger.trigger / triggerBatch now accept and forward runtimeContext; AgentClient.runRemoteTrigger / runRemoteTriggerBatch extract per-container operationIds via the existing getRequestedOperationId helper and include them in the agent payload ({id, name, operationId} for single triggers; {...container, operationId} per entry for batches); the agent-side controller runTrigger accepts an operationId in the request body (validated by triggerRequestBodySchema) and threads it into requestContainerUpdate; the agent-side batch endpoint extracts per-container operationIds into an {operationIds} runtimeContext before forwarding to the local trigger; EnqueueContainerUpdateOptions gains an operationId field honored by createAcceptedContainerUpdateRequest (single-container batches only; multi-container batches still mint per-container UUIDs); and a new AgentClient.resolveAgentOperationId helper checks the controller's operation store for an existing row at the raw (unscoped) id and reuses it when found — falling back to the toAgentScopedId form 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 to in-progress and succeeded/failed from 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-applied SSE payload to the controller carrying a full container snapshot. The controller's AgentClient.handleEvent routes this through maybeMarkAgentOperationSucceededFromAppliedPayloadmarkAgentOperationTerminalensureAgentOperationForTerminalupdateOperationStore.insertOperation + markOperationTerminal, but buildAgentOperationBase in app/agent/AgentClient.ts constructed the inserted row from {id, kind, containerName, containerId, newContainerId} only — the container snapshot was dropped on the floor. When markOperationTerminal then fired emitTerminalLifecycleEvent (app/store/update-operation.ts), the resulting emitContainerUpdateApplied / emitContainerUpdateFailed payload built by buildTerminalLifecycleEventBase lacked container. The notification handler handleContainerUpdateAppliedEvent (app/triggers/providers/Trigger.ts) then fell back to findContainerByBusinessId(containerName), which compares the agent's bare containerName (e.g. tautulli) against the controller-side fullName (e.g. mediavault_docker_tautulli) and silently dropped — the same class of findContainerByBusinessId miss 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, and maybeMarkAgentOperationFailedFromFailedPayload — stamping agent: this.name so the controller's view of the container is consistent. The dd:update-operation-changed-before-dd:update-applied race is handled by patching the container snapshot onto the existing active row via updateOperation before the terminal emit runs (only when the existing row lacks a container, never overwriting an existing snapshot). container is added to MutableUpdateOperationFields in app/store/update-operation.ts so terminal and active patches accept it. The store's terminal-lifecycle emit therefore naturally carries the agent's container into emitContainerUpdateApplied / emitContainerUpdateFailed, the payloadContainer shortcut 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.

Don't miss a new drydock release

NewReleases is sending notifications on new releases.