# Passing arguments to pipeline

**URL:** <https://forum.buildkite.community/t/passing-arguments-to-pipeline/4611>\
**Category:** Pipelines\
**Created:** [June 26, 2026, 4:41pm UTC](https://forum.buildkite.community/t/passing-arguments-to-pipeline/4611 "2026-06-26T16:41:46Z")\
**Posts on this page:** 13\
**Page:** 1

<div class="post-metadata">

**Author:** ![savvas](https://avatars.discourse-cdn.com/v4/letter/s/8e8cbc/32.png) [@savvas](https://forum.buildkite.community/u/savvas)\
**Post date:** [June 26, 2026, 4:41pm UTC](https://forum.buildkite.community/t/passing-arguments-to-pipeline/4611/1 "2026-06-26T16:41:46Z")

</div>

Hi 👋

I am trying to pass some arguments from one pipeline to another using a **trigger** step but I am unable to do so.

Suppose the following (abridged) trigger step:

```auto
- trigger: "some-other-pipeline"
  label: "My step label"
   build:
     env:
       MY_ARG: "$(buildkite-agent meta-data get my_arg)"

```

The intention here is to set **MY\_ARG** with whatever value was set to **my\_arg** with the **buildkite-agent meta-data get set** command in the same pipeline and pass that through to **some-other-pipeline**.

That however doesn’t work and **MY\_ARG** always ends up with an empty value in **some-other-pipeline** because I _believe_ shell commands aren’t getting executed inside the **env** section?

One solution that works is doing something similar to:

```auto
commands:
  - |
    cat <<YAML | buildkite-agent pipeline upload
    steps:
      - trigger: "some-other-pipeline"
        build:
          env:
            MY_ARG: "$(buildkite-agent meta-data get my_arg)"
    YAML

```

but that looks truly gruesome 🙂

Is there a better way of achieving the same effect as the above?

Thanks!

---

<div class="post-metadata">

**Author:** ![stephanie.atte](https://sea2.discourse-cdn.com/flex016/user_avatar/forum.buildkite.community/stephanie.atte/32/1357_2.png) [@stephanie.atte](https://forum.buildkite.community/u/stephanie.atte)\
**Post date:** [June 26, 2026, 5:31pm UTC](https://forum.buildkite.community/t/passing-arguments-to-pipeline/4611/2 "2026-06-26T17:31:04Z")

</div>

Hello @savvas

Welcome to the community 🎉 !

Your understanding is correct. Shell commands aren’t evaluated in a static trigger step’s `env` block it’s treated as pipeline config, not shell input, so `$(buildkite-agent meta-data get "my_arg")` won’t run there, leaving `MY_ARG` empty.

Your dynamic pipeline upload approach is the best and recommended approach for runtime values , so the resolved value (e.g. `MY_ARG: "1.1"`) gets passed through as seen in my example below.

```yaml
steps:
  - command: 'buildkite-agent meta-data set "my_arg" "1.1"'
  - wait
  - label: "Generate trigger step"
    command: |
      cat <<YAML | buildkite-agent pipeline upload
      steps:
        - trigger: "some-other-pipeline"
          build:
            env:
              MY_ARG: "$(buildkite-agent meta-data get "my_arg")"
      YAML

```

---

<div class="post-metadata">

**Author:** ![savvas](https://avatars.discourse-cdn.com/v4/letter/s/8e8cbc/32.png) [@savvas](https://forum.buildkite.community/u/savvas)\
**Post date:** [June 28, 2026, 6:24pm UTC](https://forum.buildkite.community/t/passing-arguments-to-pipeline/4611/3 "2026-06-28T18:24:05Z")

</div>

Hi @stephanie.atte, thanks for your quick reply!

Ok I see thanks for confirming. 👍

I’ve noticed that static arguments declared either in an `env` block at the top of the pipeline file or even in a `select` block are also visible within the `trigger` block using the `{metaenv?.MY_VAR}` syntax but I guess this isn’t shell but rather buildkite level resolution?

---

<div class="post-metadata">

**Author:** ![benmc](https://sea2.discourse-cdn.com/flex016/user_avatar/forum.buildkite.community/benmc/32/1159_2.png) [@benmc](https://forum.buildkite.community/u/benmc)\
**Post date:** [June 29, 2026, 2:55am UTC](https://forum.buildkite.community/t/passing-arguments-to-pipeline/4611/4 "2026-06-29T02:55:58Z")

</div>

@savvas that’s correct; we’re essentially using a YAML parser via Ruby to determine what the values are on the pipeline where `input` and `env` are being set, then interpolating those values in the pipeline YAML.

---

<div class="post-metadata">

**Author:** ![ohalligon](https://sea2.discourse-cdn.com/flex016/user_avatar/forum.buildkite.community/ohalligon/32/1254_2.png) [@ohalligon](https://forum.buildkite.community/u/ohalligon)\
**Post date:** [July 17, 2026, 2:48pm UTC](https://forum.buildkite.community/t/passing-arguments-to-pipeline/4611/5 "2026-07-17T14:48:21Z")

</div>

As I was reading this thread, I was wondering if an alternate solution for the OP couldn’t be to read the `my_arg` metadata of the calling build… directly from the triggered build?

i.e. calling `MY_ARG=$(buildkite-agent meta-data get --build "$BUILDKITE_TRIGGERED_FROM_BUILD_ID" my_arg)` directly within the script/command of the `some-other-pipeline` build that they triggered and that needs to use that `MY_ARG` value? Would that work too?

(See [buildkite-agent meta-data | Buildkite Documentation](https://buildkite.com/docs/agent/cli/reference/meta-data#build) & [Environment variables | Buildkite Documentation](https://buildkite.com/docs/pipelines/configure/environment-variables#BUILDKITE_TRIGGERED_FROM_BUILD_ID))

---

<div class="post-metadata">

**Author:** ![stephanie.atte](https://sea2.discourse-cdn.com/flex016/user_avatar/forum.buildkite.community/stephanie.atte/32/1357_2.png) [@stephanie.atte](https://forum.buildkite.community/u/stephanie.atte)\
**Post date:** [July 17, 2026, 5:25pm UTC](https://forum.buildkite.community/t/passing-arguments-to-pipeline/4611/6 "2026-07-17T17:25:56Z")

</div>

Hey @ohalligon 👋 yes, that works too

yaml

```auto
steps:
  - label: ":mag: Get Parent Build Meta-data"
    command: |
      MY_ARG="$(buildkite-agent meta-data get --build "$BUILDKITE_TRIGGERED_FROM_BUILD_ID" my_arg)"
      echo "The ARG is $$MY_ARG"

```

Small gotcha to watch out for: you need `$$MY_ARG` (double `$`) when referencing it later, otherwise Buildkite’s own variable interpolation strips it out before the shell even runs (it doesn’t recognize `MY_ARG` since it’s not a real Buildkite variable).

One downside compared to the `build.env` option though `MY_ARG` here is just a shell variable scoped to that one step. It won’t carry over to other steps in the pipeline, and it won’t show up as an actual env var in the UI. If you want it available across the whole triggered build (and visible as a proper env var), setting it via `build.env` on the trigger step itself is the more convenient route.

---

<div class="post-metadata">

**Author:** ![savvas](https://avatars.discourse-cdn.com/v4/letter/s/8e8cbc/32.png) [@savvas](https://forum.buildkite.community/u/savvas)\
**Post date:** [July 17, 2026, 9:18pm UTC](https://forum.buildkite.community/t/passing-arguments-to-pipeline/4611/7 "2026-07-17T21:18:30Z")

</div>

Oh, that actually sounds quite elegant and I think fits well with my use case!

I keep forgetting how powerful the built-in Buildkite variables are. 🙂

Thanks for sharing that. 🙏

---

<div class="post-metadata">

**Author:** ![savvas](https://avatars.discourse-cdn.com/v4/letter/s/8e8cbc/32.png) [@savvas](https://forum.buildkite.community/u/savvas)\
**Post date:** [July 17, 2026, 9:32pm UTC](https://forum.buildkite.community/t/passing-arguments-to-pipeline/4611/8 "2026-07-17T21:32:13Z")

</div>

Right, I see..👍

So, if that needed to be propagated further to a downstream pipeline then I guess one would have to `buildkite-agent meta-data set` it on this step and then grab it again using the `$BUILDKITE_TRIGGERED_FROM_BUILD_ID` from there?

Re the `$$` I must admit that has tripped us up a few times so far.. 😊 So, my understanding is that upon the first pass Buildkite will stip the leading `$` and attempt to match the remaining against it’s own environment variables therefore converting a declared variable of say `$MY_VAR` to merely `MY_VAR` for the shell to resolve, which it naturally can’t find, is that correct?

Thanks!

---

<div class="post-metadata">

**Author:** ![ohalligon](https://sea2.discourse-cdn.com/flex016/user_avatar/forum.buildkite.community/ohalligon/32/1254_2.png) [@ohalligon](https://forum.buildkite.community/u/ohalligon)\
**Post date:** [July 17, 2026, 10:12pm UTC](https://forum.buildkite.community/t/passing-arguments-to-pipeline/4611/9 "2026-07-17T22:12:39Z")

</div>

You can read more about this in [Environment variables | Buildkite Documentation](https://buildkite.com/docs/pipelines/configure/environment-variables#runtime-variable-interpolation) and [buildkite-agent pipeline | Buildkite Documentation](https://buildkite.com/docs/agent/cli/reference/pipeline#environment-variable-substitution) but yes, basically what happens is that:

- During `buildkite-agent pipeline upload`, i.e. when Buildkite parses your `pipeline.yml` file and uploads it to generate the actual jobs for your build based on its configuration, it pre-processes that `pipeline.yml` file first to substitute any `$VARIABLE` in it with the corresponding value
- At the time this `buildkite-agent pipeline upload` runs, the environment will already contain all the `BUILDKITE_*` variables defined [in this doc](https://buildkite.com/docs/pipelines/configure/environment-variables#buildkite-environment-variables), as well as all environment variables declared upstream (e.g. passed by an `env:` attribute in the parent job in which you had your `trigger:` for example. Or any environment variable you might have set when triggering a CI build manually via the “New Build” green button from the Buildkite UI then opening the “Options \> Environment variable” box before validating.
- So at that stage, any instance of e.g. `$BUILDKITE_BRANCH` in your `pipeline.yml` will be interpolated by `buildkite-agent pipeline upload` during that preprocessing and replaced by its value. And same for instance of `$FOO` if the `FOO` env var is defined **at the time of this pipeline-upload stage** (e.g. because it was passed by `env:` from the triggering build or provided manually when you used the “New Build” button).
- And later, way after your steps have been uploaded and only at the time your job will later be picked up by an available agent, that agent will run that `command` in a shell… which might trigger a second stage of shell interpolation (but this time evaluated at the time your job **runs** )

So there’s one interpolation at “pipeline upload” time by buildkite, then the usual interpolation by your shell when the `command` of your job is actually run.

And using `$$` (or `\$`, [both work](https://buildkite.com/docs/agent/cli/reference/pipeline#environment-variable-substitution-escaping-the-dollars-character)) in your `pipeline.yml` file is a way to escape that `$` character so that `pipeline-upload` replaces it with a verbatim `$` instead of interpolating the variable.

If that helps that’s similar to if like if you were to use `echo "echo \$FOO" >myscript.sh` in a terminal to echo a verbatim `echo $FOO` in your script that would then only interpolate the value of the `FOO` variable when the `myscript.sh` is finally run,

---

<div class="post-metadata">

**Author:** ![ohalligon](https://sea2.discourse-cdn.com/flex016/user_avatar/forum.buildkite.community/ohalligon/32/1254_2.png) [@ohalligon](https://forum.buildkite.community/u/ohalligon)\
**Post date:** [July 17, 2026, 10:29pm UTC](https://forum.buildkite.community/t/passing-arguments-to-pipeline/4611/10 "2026-07-17T22:29:05Z")

</div>

By the way, another way to avoid needing to escape the `$` in your `pipeline.yml` via `$$` or `\$` —so that the `$` is kept verbatim until the command runs and is interpolated by the running shell in your agent—is to not have your command containing that `echo $MY_VAR` or whatever _directly_ in your `pipeline.yml` file, but instead put it in a separate script, e.g. instead of:

```yaml
# pipeline.yml
steps:
  - label: arg-demo
    command: |
      MY_ARG="$(buildkite-agent meta-data get --build "$BUILDKITE_TRIGGERED_FROM_BUILD_ID" my_arg)"
      # Be sure to escape the dollar sign by doubleing it or using a backslash
      # So that the preprocessing of this pipeline.yml file during upload
      # just replaces it with a literal single dollar sign (so that it can then
      # only be interpolated at the time the shell runs this command when
      # your job runs) instead of interpolating the variable too soon.
      echo "The ARG is $$MY_ARG"

```

You can instead move the code from your `command` in a dedicated script file (let’s call it `arg-demo.sh`) and call it in your YAML’s `command:` attribute:

```bash
#!/bin/env bash
# arg-demo.sh

MY_ARG="$(buildkite-agent meta-data get --build "$BUILDKITE_TRIGGERED_FROM_BUILD_ID" my_arg)"
# no deed for escaping the `$` anymore in the code below,
# thanks to the indirection we used of putting this in a separate script, and
# not directly into the `pipeline.yml` that gets processed by pipeline upload
echo "The ARG is $MY_ARG"

```

```yaml
# pipeline.yml
steps:
  - label: arg-demo
    command: arg-demo.sh

```

That way there’s no `$` —that risks being interpolated too soon by the `pipeline upload` step in the `pipeline.yml` —anymore.

> **PS: Note that a subtle difference between the first approach (code directly in your pipeline.yml ) vs my example above (code in separate script) is that in the latter the $BUILDKITE\_TRIGGERED\_FROM\_BUILD\_ID env var is evaluated at the time your job runs (and that arg-demo.sh script is run), while on the former it was evaluated as soon as your pipeline.yml is processed during pipeline upload**
>
> - In that particular case that shouldn’t matter, because that `BUILDKITE_TRIGGERED_FROM_BUILD_ID` env var is visible both during the pipeline upload… and at the time the job executes the command.
> - That is not the same for the `MY_ARG` (and why you need to escape it with `$$MY_ARG` or `\$MY_ARG` if you put it in the `pipeline.yml`), which is not defined at the time your `pipeline.yml` is pre-processed by the `pipeline upload` (hence why just keeping `$MY_ARG` without escape in the `pipeline.yml` would be interpolated to the value of `MY_VAR` at that time… which is… nothing) and is only defined by the shell code when that code is finally executed when your job _runs._

---

<div class="post-metadata">

**Author:** ![savvas](https://avatars.discourse-cdn.com/v4/letter/s/8e8cbc/32.png) [@savvas](https://forum.buildkite.community/u/savvas)\
**Post date:** [July 17, 2026, 10:44pm UTC](https://forum.buildkite.community/t/passing-arguments-to-pipeline/4611/11 "2026-07-17T22:44:34Z")

</div>

I see, gotcha 👍 So, first process all these sources on a pre-upload stage and then run the declared command steps on some agent which naturally has no connection/context to the triggering/uploading environment as it runs on it’s own vm/host, I assume.

Cool, thanks for sharing these details!

---

<div class="post-metadata">

**Author:** ![savvas](https://avatars.discourse-cdn.com/v4/letter/s/8e8cbc/32.png) [@savvas](https://forum.buildkite.community/u/savvas)\
**Post date:** [July 17, 2026, 10:51pm UTC](https://forum.buildkite.community/t/passing-arguments-to-pipeline/4611/12 "2026-07-17T22:51:27Z")

</div>

Right, I see..that’s actually pretty cool as it can also help improve readability significantly on pipeline.yaml, especially if that’s rather complex! (naturally, at the expense of having a few more shell scripts to manage but I think with some careful naming conventions that shouldn’t be too much of an issue).

Nice one, thank you. 👍

---

<div class="post-metadata">

**Author:** ![system](https://sea2.discourse-cdn.com/flex016/user_avatar/forum.buildkite.community/system/32/1321_2.png) [@system](https://forum.buildkite.community/u/system)\
**Post date:** [August 17, 2026, 8:52am UTC](https://forum.buildkite.community/t/passing-arguments-to-pipeline/4611/13 "2026-08-17T08:52:09Z")

</div>

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.
