Argo Workflows on Main Fixes Trace Parents and Archived Log Names


Argo Workflows is the Kubernetes controller for step and DAG workflows, including CronWorkflow schedules used by batch pipelines. In the week ending 13 October 2026, main took 50 commits, 209 files, 3199 insertions, and 2512 deletions. The paths touched most often are go.mod, go.sum, and gomod2nix.toml. Most commits are dependency bumps. A few change what a running workflow reports.

The emissary is the argoexec process that starts the user command as a child. The controller injects a TRACEPARENT for a pod level span. The emissary then starts the main container span. The user process is supposed to see that span as its parent.

Until the emissary fix, injectTraceParent called os.Setenv on the emissary itself. That mutates variables the OpenTelemetry environment carrier spec treats as immutable input from process start. Spans opened inside argoexec after the call then sat under the child span, not the pod span.

The function now appends TRACEPARENT through envcar.Carrier and returns the slice. cmd/argoexec/commands/emissary.go assigns that slice to exec.Cmd. A repeated key keeps the last value, so the appended variable overrides the pod level one copied from os.Environ().

command.Env = injectTraceParent(ctx, os.Environ())

With no span, the slice is unchanged. Tests check that the new value follows any pod TRACEPARENT, and that os.Getenv in the emissary still returns the original. A process that reads W3C trace context parents its spans on the main container span. One that ignores TRACEPARENT is unaffected.

The UI opened logs for a container named main, and it read the archived artifact only when that name was exactly main. A container set stores one <container>-logs artifact per container, in declaration order. After the pod is gone, a cluster read of main is empty, and a name such as step-1 was never requested.

The archived log fix adds getMainContainerNames. No node, or a template that is not a container set, still means main. A container set returns the declared names. The logs button and the details side panel pass the first name. A named deep link is unchanged. An empty container used to force main, and on a container set it now opens the first declared name.

getContainerLogs reloads the workflow and checks hasArtifactLogs for the requested container once the node has finished. Running nodes still stream from the cluster. Artifact failure falls back to the cluster, and cluster failure falls back to the reloaded artifacts. The old code could miss the artifact on a stale status, then request a pod that was already gone.

The event flow page stopped loading every workflow labeled events.argoproj.io/sensor or events.argoproj.io/trigger. Archived workflows are included, and the comment says an unbounded query is expensive for the database. The first request sets listOptions.limit to 500. The footnote says up to 500 workflows across all triggers load initially, plus any created while the page is open. A trigger with no recent run may show none.

Validation already skipped a templateRef when the template field was still an expression. Issue 16008 is the resource name. A name such as {{inputs.parameters.workflow-name}}, including one with a static prefix, is replaced by template.PlaceholderPrefix (__argo__internal__placeholder-) before lookup. VerifyResolvedVariables returns early only while {{ }} is still present. The placeholder has no braces, so the getter was called with a name that cannot exist.

A coded not found error then inspected tmplRef.Template. That field is often the literal main, so the old skip did not fire and submit failed. Offline argo lint returns a plain error, and that error was returned as well. Both clients rejected a reference that is valid once the parameter is set.

The validation change in workflow/validate/validate.go returns before the getter:

if strings.Contains(tmplRef.Name, template.PlaceholderPrefix) {
    // internal placeholder indicates the referenced template is dynamic
    return nil, nil
}

Tests cover steps and a cluster scoped DAG, including the plain error getter used by offline lint. A missing static name is still rejected. A name that exists only at runtime is no longer rejected by lint.

go.mod changed in 25 commits this window. Two are the security bump. The root module moves golang.org/x/net from v0.59.0 to v0.60.0. The Go SDK modules move from v0.58.0 to v0.60.0. Renovate names five net/http HTTP/2 issues and assigns no severity. For argo-server, the server bugs are the ones that matter:

  • A Trailer header that declares many fields can grow memory and bypass MaxHeaderBytes (CVE-2026-78659).
  • A stream reset followed by a read can refund connection flow control twice (CVE-2026-78663).
  • Repeated SETTINGS_INITIAL_WINDOW_SIZE frames burn CPU (CVE-2026-78669).
  • An unsynchronized HPACK encoder update can crash the process (CVE-2026-97032).

CVE-2026-78660 is the client case, malformed framing headers forwarded to an HTTP/1 peer, and it matters when the process is proxying.

The v4.1.5 changelog landed on main on 9 October. It records the release branch x/net bump, configured PostgreSQL sslmode, database queries bound to the caller context, termination of a suspended workflow, sync queue deletes limited to the owning controller (issue 16737), the memoized resubmit node ID fix, and deterministic span ids. It does not record the child TRACEPARENT change, archived log names, the dynamic templateRef skip, or the 500 workflow cap. Those are on main only as of 13 October.

The toolchain commit points the e2e client at http.Protocols for cleartext HTTP/2 and drops deprecated http2.Transport. v4.1.5 cherry picks that drop. The server listener is unchanged. mockery and golangci-lint run with GOTOOLCHAIN from go.mod.

Example YAML moved off xeipuuv/gojsonschema in the jsonschema v6 swap, onto github.com/santhosh-tekuri/jsonschema/v6 v6.0.2. It compiles the kind definition in schema.json. AssertFormat() stays on because draft 2020-12 would otherwise skip formats. date-time is still checked. int32 and byte are not. Integral httpGet.port and podDisruptionBudget.minAvailable stay legal. A fractional number does not. Unknown kinds fail per file, and gojsonschema stays an indirect require. Controller admission does not call this code.

Five commits on 13 October only move fixtures out of Go strings. The node ID collision fixtures now sit next to the collision test. They still pin issue 16376, sibling names whose FNV-1a ids match because the hash is not finalized, and a deferred DAG task that must not be stored before its node exists. Controller behavior does not change.

On release 4.1, take v4.1.5 for the x/net bump and for suspended workflow termination. Child TRACEPARENT, container set log names, and lint of a dynamic templateRef name are on main, and they are absent from the v4.1.5 selected list.