Tekton Pipelines can pass results between tasks, but the mechanism is a bit more implicit than you might expect, relying on shared storage and environment variables rather than explicit "return values."

Let’s see this in action. Imagine a simple pipeline with two tasks: the first task generates a value, and the second task consumes it.

apiVersion: tekton.dev/v1beta1
kind: Pipeline
metadata:
  name: pass-results-pipeline
spec:
  tasks:
    - name: generate-value
      taskSpec:
        steps:
          - name: generate
            image: ubuntu
            script: |
              echo "my_secret_value_123" > $(results.my-output.path)
        results:
          - name: my-output
            description: The generated value
            type: string

    - name: consume-value
      taskSpec:
        params:
          - name: input-value
            type: string
        steps:
          - name: consume
            image: ubuntu
            script: |
              echo "The consumed value is: $(params.input-value)"
      runAfter:
        - generate-value
  # This is where the magic happens: linking results to params
  finally:
    - name: link-results-to-params
      taskSpec:
        steps:
          - name: link
            image: alpine
            script: |
              echo "Linking $(tasks.generate-value.results.my-output) to $(tasks.consume-value.params.input-value)"

This example shows a conceptual link, but Tekton doesn’t have a finally block to directly re-assign task results to other task parameters within the Pipeline definition itself. The standard way to achieve this is by declaring the result in the upstream task and then referencing it as a parameter in the downstream task.

Here’s how it actually works with a correct Pipeline definition:

apiVersion: tekton.dev/v1beta1
kind: Pipeline
metadata:
  name: pass-results-pipeline
spec:
  tasks:
    - name: generate-value
      taskRef: # Or taskSpec if defined inline
        name: generate-task # Assuming generate-task is a Task resource
      results:
        - name: my-output
          from:
            - $(tasks.generate-value.results.my-output) # This syntax is incorrect for linking within the Pipeline spec itself.

    - name: consume-value
      taskRef: # Or taskSpec if defined inline
        name: consume-task # Assuming consume-task is a Task resource
      params:
        - name: input-value
          value: $(tasks.generate-value.results.my-output) # THIS is how you pass results!
      runAfter:
        - generate-value

Let’s break down the mental model. A Tekton Task can declare results. When a Task runs as part of a Pipeline, it writes its declared results to a specific location on the persistent volume shared by the Pipeline Run. This location is determined by Tekton and is typically within a directory like /tekton/results/. The actual filename corresponds to the result’s name.

So, in our generate-value task, the script echo "my_secret_value_123" > $(results.my-output.path) writes the string "my_secret_value_123" into a file named my-output within the /tekton/results/ directory.

Now, for the consume-value task to use this, it needs to declare a param that expects this value. In the Pipeline definition, we then link the result from the generate-value task to the param of the consume-value task using the value field: value: $(tasks.generate-value.results.my-output).

When the consume-value task starts, Tekton intercepts this parameter substitution. It reads the content of the /tekton/results/my-output file (which was written by the generate-value task) and injects it as the value for the input-value parameter. The consume step in the consume-value task then sees $(params.input-value) resolved to "my_secret_value_123".

The key insight here is that results are not "returned" in a programmatic sense. They are files written to a shared filesystem, and parameters are substituted with the contents of those files. This makes the system robust and allows for passing complex data structures if needed (though strings are the most common).

The $(results.my-output.path) syntax is used within a Task’s steps to know where to write the result. The $(tasks.<task-name>.results.<result-name>) syntax is used within a Pipeline definition to reference that written result and pass it to another task’s parameter.

The most surprising thing about this mechanism is how it abstracts away the underlying storage. You don’t explicitly manage volumes for passing results; Tekton handles the mounting and path management for you, making it appear as if results are directly "passed."

If you were to define a Task that produces a result but then forget to declare that result in the TaskSpec of the upstream Task, the Pipeline would fail with an error indicating an unknown result.

Want structured learning?

Take the full Tekton course →