depends_on gate can report success after a dependency failure: Task.DepStatus is never persisted (v3.18.1)
Task.DepStatus updates from server/queue/fifo.go::updateDepStatusInQueue are
never written back to the persistent task store (no store.TaskUpdate exists --
only TaskInsert/TaskDelete), so a workflow whose dependency already finished
before a queue reload (server restart, HA leader change) sees an empty
DepStatus and Task.ShouldRun() vacuously returns true regardless of the
dependency's actual result.
Repro
Pipeline 5543, workflow 39994 (a depends_on aggregate "gate" workflow)
depending on workflow 39997 (a required dependency workflow), which reached
terminal failure at unix time 1789479910. Workflow 39994 started at
1789480893 (~983s / ~16 min later) and reported success.
Root cause (traced against a shallow clone of v3.18.1)
pipeline/frontend/builder/builder.gocomputesitem.RunsOnviaIncludesStatusSuccess/IncludesStatusFailure.pipeline/frontend/yaml/constraint/constraint.go'sConstraint.Matchnever reads workflow status, so an explicitwhen: status: [success]on the dependent workflow does not change gating behavior (proven no-op).server/model/task.go:70Task.ShouldRun()loopsfor _, status := range t.DepStatus-- an empty map ranges zero times and the function returnstrue.server/queue/fifo.go::updateDepStatusInQueueupdatesDepStatusonly on the in-memory queue.server/store/store.goexposes onlyTaskInsert(called once at enqueue time with an emptyDepStatus) andTaskDelete(called once on finish/poll) for tasks -- there is noTaskUpdate.server/queue/persistent.go::WithTaskStorereloads the queue from the store on startup/leader change, which restores tasks with their stale, emptyDepStatusfrom insert time if they are still queued.
Impact
A depends_on aggregate gate workflow (a common pattern for exposing one
required branch-protection status context while fanning out to several
path-filtered or parallel workflows) can report success even though a
required dependency already failed, if the server restarts or an HA leader
change happens while the gate workflow is still queued. This defeats
branch-protection gating silently -- there is no error, just a wrong success.
Environment
- woodpecker-server v3.18.1
- Confirmed via
GET /api/repos/{id}/pipelines/{number}timestamps on the reproduction above, not merely inferred from logs.
Suggested fix direction
Persist DepStatus writes (a TaskUpdate on the store, called from
updateDepStatusInQueue or equivalent) so a queue reload restores the real
dependency state instead of an empty map, or make Task.ShouldRun() on an
empty DepStatus re-derive it from already-terminal sibling workflows in the
same pipeline instead of assuming success.
Source: woodpecker-ci/woodpecker