-
Notifications
You must be signed in to change notification settings - Fork 1
Migrate CI from GitHub Actions to Buildkite #48
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 12 commits
9e1cd68
1852956
2a4b799
17b5270
69573df
90290de
c1c5d36
29ae8dd
3b86d09
5d2f981
6ae487c
0f11d0b
4f257bd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| # Qodana code quality pipeline — replaces .github/workflows/qodana_code_quality.yml | ||
| # Triggered on pushes to main/releases/* and on pull requests. | ||
| # Skips documentation-only changes (CHANGELOG.md, CONTRIBUTING.md, CODE_OF_CONDUCT.md, README.md, img/**). | ||
|
|
||
| steps: | ||
| - label: ":qodana: Qodana Scan" | ||
| key: qodana | ||
| agents: | ||
| queue: default | ||
| env: | ||
| QODANA_TOKEN: $${QODANA_TOKEN?} | ||
| QODANA_ENDPOINT: "https://qodana.cloud" | ||
| commands: | ||
| - | | ||
| # Skip if all changed files are documentation-only | ||
| CHANGED="$$(git diff --name-only HEAD~1 HEAD 2>/dev/null || git diff --name-only origin/main...HEAD)" | ||
| NON_DOC="$$(echo "$$CHANGED" | grep -vE '^(CHANGELOG|CONTRIBUTING|CODE_OF_CONDUCT|README)\.md$$' | grep -v '^img/')" | ||
| if [ -z "$$NON_DOC" ]; then | ||
|
Comment on lines
+16
to
+18
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Diff the full PR range before deciding to skip Qodana.
Proposed fix- CHANGED="$$(git diff --name-only HEAD~1 HEAD 2>/dev/null || git diff --name-only origin/main...HEAD)"
- NON_DOC="$$(echo "$$CHANGED" | grep -vE '^(CHANGELOG|CONTRIBUTING|CODE_OF_CONDUCT|README)\.md$$' | grep -v '^img/')"
+ if [ "$${BUILDKITE_PULL_REQUEST:-false}" != "false" ]; then
+ CHANGED="$$(git diff --name-only origin/main...HEAD)"
+ else
+ CHANGED="$$(git diff --name-only HEAD~1 HEAD 2>/dev/null || git diff --name-only origin/main...HEAD)"
+ fi
+ NON_DOC="$$(echo "$$CHANGED" | grep -vE '^(CHANGELOG|CONTRIBUTING|CODE_OF_CONDUCT|README)\.md$$' | grep -v '^img/' || true)"🤖 Prompt for AI Agents |
||
| echo "Only documentation changes detected — skipping Qodana scan." | ||
| exit 0 | ||
| fi | ||
| - | | ||
| # Run Qodana via Docker (mirrors the JetBrains/qodana-action behavior) | ||
| docker run --rm \ | ||
| -v "$$(pwd):/data/project" \ | ||
| -e QODANA_TOKEN="$$QODANA_TOKEN" \ | ||
| -e QODANA_ENDPOINT="$$QODANA_ENDPOINT" \ | ||
| jetbrains/qodana-jvm:2025.3 \ | ||
| --save-report \ | ||
| --results-dir /data/project/build/qodana/results \ | ||
| --report-dir /data/project/build/qodana/report | ||
| artifact_paths: | ||
| - "build/qodana/report/**/*" | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,59 @@ | ||||||||||||||||||||||
| # Release pipeline — replaces .github/workflows/release.yml | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| steps: | ||||||||||||||||||||||
| - label: ":rocket: Publish Plugin" | ||||||||||||||||||||||
| key: publish | ||||||||||||||||||||||
| env: | ||||||||||||||||||||||
| PUBLISH_TOKEN: $${PUBLISH_TOKEN?} | ||||||||||||||||||||||
| CERTIFICATE_CHAIN: $${CERTIFICATE_CHAIN?} | ||||||||||||||||||||||
| PRIVATE_KEY: $${PRIVATE_KEY?} | ||||||||||||||||||||||
| PRIVATE_KEY_PASSWORD: $${PRIVATE_KEY_PASSWORD?} | ||||||||||||||||||||||
| GITHUB_TOKEN: $${GITHUB_TOKEN?} | ||||||||||||||||||||||
| plugins: | ||||||||||||||||||||||
| - docker#v5.12.0: | ||||||||||||||||||||||
| image: "gradle:8-jdk21" | ||||||||||||||||||||||
| environment: | ||||||||||||||||||||||
| - PUBLISH_TOKEN | ||||||||||||||||||||||
| - CERTIFICATE_CHAIN | ||||||||||||||||||||||
| - PRIVATE_KEY | ||||||||||||||||||||||
| - PRIVATE_KEY_PASSWORD | ||||||||||||||||||||||
| commands: | ||||||||||||||||||||||
|
Comment on lines
+6
to
+20
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Checks whether the release container image has GitHub CLI available.
docker run --rm --entrypoint sh gradle:8-jdk21 -lc 'command -v gh && gh --version'Repository: z8dn/advanced-android-project-view Length of output: 124 🏁 Script executed: cat -n .buildkite/pipeline.release.ymlRepository: z8dn/advanced-android-project-view Length of output: 2321 🏁 Script executed: # Search for uses of gh command and GITHUB_TOKEN in the codebase
rg 'gh ' --type yaml
rg 'GITHUB_TOKEN' --type yamlRepository: z8dn/advanced-android-project-view Length of output: 60 🏁 Script executed: # Check if there are other buildkite pipeline files or docs
fd 'pipeline' --type fRepository: z8dn/advanced-android-project-view Length of output: 60 🏁 Script executed: # Look for Dockerfile, docker-compose, or other Docker configs
fd -e Dockerfile -e dockerfile
fd -e 'docker-compose*'Repository: z8dn/advanced-android-project-view Length of output: 60 🏁 Script executed: # Check if there are any scripts that set up the environment
fd -e 'sh' -e 'bash' | head -20Repository: z8dn/advanced-android-project-view Length of output: 60 🏁 Script executed: # Search for any setup steps or gh installation references
rg 'gh ' -A 2 -B 2 --type yaml --type sh
rg 'docker' .buildkite/ -iRepository: z8dn/advanced-android-project-view Length of output: 444 Add GITHUB_TOKEN to Docker container environment and ensure GitHub CLI is available in the release image. The Add Proposed fix plugins:
- docker#v5.12.0:
- image: "gradle:8-jdk21"
+ image: "<image-with-gradle-jdk21-and-gh>"
environment:
- PUBLISH_TOKEN
- CERTIFICATE_CHAIN
- PRIVATE_KEY
- PRIVATE_KEY_PASSWORD
+ - GITHUB_TOKEN🤖 Prompt for AI Agents |
||||||||||||||||||||||
| - | | ||||||||||||||||||||||
| TAG="$$BUILDKITE_TAG" | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| if [ -z "$$RELEASE_BODY" ]; then | ||||||||||||||||||||||
| RELEASE_BODY="$$(gh release view "$$TAG" --json body -q '.body')" | ||||||||||||||||||||||
|
Comment on lines
+21
to
+25
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fail fast when the release pipeline is not running for a tag.
Proposed fix- TAG="$$BUILDKITE_TAG"
+ TAG="$${BUILDKITE_TAG:?Release pipeline must run on a tag build}"📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||
| fi | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| CHANGELOG="$$(echo "$$RELEASE_BODY" | sed -e 's/^[[:space:]]*$$//g' -e '/./,$$!d')" | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| if [ -n "$$CHANGELOG" ]; then | ||||||||||||||||||||||
| gradle patchChangelog --release-note="$$CHANGELOG" | ||||||||||||||||||||||
| fi | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| gradle publishPlugin | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| gh release upload "$$TAG" ./build/distributions/* | ||||||||||||||||||||||
|
Comment on lines
+30
to
+36
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Description: Verify release pipeline Gradle invocations against the wrapper version.
set -euo pipefail
echo "Wrapper Gradle distribution:"
fd -a 'gradle-wrapper.properties' . | xargs -r sed -n '1,20p'
echo
echo "Buildkite Gradle invocations:"
rg -n -C2 '\bgradle\b|\.\/gradlew|gradlew\.bat' --glob '.buildkite/*.yml'Repository: z8dn/advanced-android-project-view Length of output: 521 🏁 Script executed: #!/bin/bash
# Fix: Use proper escaping for rg regex pattern
set -euo pipefail
echo "Checking .buildkite/pipeline.release.yml around lines 30-36:"
head -40 .buildkite/pipeline.release.yml | tail -15
echo
echo "Searching for gradle invocations in .buildkite/ directory:"
rg 'gradle|gradlew' '.buildkite/' -nRepository: z8dn/advanced-android-project-view Length of output: 1860 Use The release pipeline currently runs Lines 31 and 34 in
🤖 Prompt for AI Agents |
||||||||||||||||||||||
|
|
||||||||||||||||||||||
| if [ -n "$$CHANGELOG" ]; then | ||||||||||||||||||||||
| BRANCH="changelog-update-$$TAG" | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| git config user.email "buildkite@ci" | ||||||||||||||||||||||
| git config user.name "Buildkite" | ||||||||||||||||||||||
| git checkout -b "$$BRANCH" | ||||||||||||||||||||||
| git commit -am "Changelog update - $$TAG" | ||||||||||||||||||||||
| git push --set-upstream origin "$$BRANCH" | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| gh label create "release changelog" \ | ||||||||||||||||||||||
| --description "Pull requests with release changelog update" \ | ||||||||||||||||||||||
| --force || true | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| gh pr create \ | ||||||||||||||||||||||
| --title "Changelog update - \`$$TAG\`" \ | ||||||||||||||||||||||
| --body "Current pull request contains patched \`CHANGELOG.md\` file for the \`$$TAG\` version." \ | ||||||||||||||||||||||
| --label "release changelog" \ | ||||||||||||||||||||||
| --head "$$BRANCH" | ||||||||||||||||||||||
| fi | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| artifact_paths: | ||||||||||||||||||||||
| - "build/distributions/*.zip" | ||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,74 @@ | ||
| # UI test pipeline — replaces .github/workflows/run-ui-tests.yml | ||
| # Triggered manually via `buildkite-agent pipeline upload .buildkite/pipeline.ui-tests.yml` | ||
| # or by triggering the pipeline from the Buildkite UI. | ||
|
|
||
| steps: | ||
| # ─── Linux ──────────────────────────────────────────────────────────────── | ||
| - label: ":linux: UI Tests (Linux)" | ||
| key: ui-test-linux | ||
| agents: | ||
| os: linux | ||
| queue: default | ||
| env: | ||
| JAVA_HOME: /usr/lib/jvm/zulu-21-amd64 | ||
| DISPLAY: ":99.0" | ||
| commands: | ||
| - Xvfb -ac :99 -screen 0 1920x1080x16 & | ||
| - ./gradlew runIdeForUiTests & | ||
| - | | ||
| # Wait for IDE to be ready (equivalent to url-health-check-action) | ||
| for i in $$(seq 1 15); do | ||
| if curl -sf http://127.0.0.1:8082 > /dev/null 2>&1; then | ||
| echo "IDE is ready" | ||
| break | ||
| fi | ||
| echo "Attempt $$i/15 — waiting 30s..." | ||
| sleep 30 | ||
| done | ||
| - ./gradlew test | ||
|
Comment on lines
+18
to
+28
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fail the UI step when the IDE never becomes ready. All three readiness loops fall through after 15 failed attempts and still run tests. Add an explicit timeout failure before invoking Proposed fix # Wait for IDE to be ready (equivalent to url-health-check-action)
+ READY=0
for i in $$(seq 1 15); do
if curl -sf http://127.0.0.1:8082 > /dev/null 2>&1; then
echo "IDE is ready"
+ READY=1
break
fi
echo "Attempt $$i/15 — waiting 30s..."
sleep 30
done
+ if [ "$$READY" -ne 1 ]; then
+ echo "IDE did not become ready after 15 attempts"
+ exit 1
+ fiApply the same for /L %%i in (1,1,15) do (
curl -sf http://127.0.0.1:8082 >nul 2>&1 && (echo IDE is ready & goto :ready)
echo Attempt %%i/15 - waiting 30s...
timeout /t 30 /nobreak >nul
)
+ echo IDE did not become ready after 15 attempts
+ exit /b 1
:readyAlso applies to: 42-51, 65-72 🤖 Prompt for AI Agents |
||
| artifact_paths: | ||
| - "build/reports/tests/**/*" | ||
|
|
||
| # ─── macOS ──────────────────────────────────────────────────────────────── | ||
| - label: ":mac: UI Tests (macOS)" | ||
| key: ui-test-macos | ||
| agents: | ||
| os: macos | ||
| queue: default | ||
| env: | ||
| JAVA_HOME: /Library/Java/JavaVirtualMachines/zulu-21.jdk/Contents/Home | ||
| commands: | ||
| - ./gradlew runIdeForUiTests & | ||
| - | | ||
| for i in $$(seq 1 15); do | ||
| if curl -sf http://127.0.0.1:8082 > /dev/null 2>&1; then | ||
| echo "IDE is ready" | ||
| break | ||
| fi | ||
| echo "Attempt $$i/15 — waiting 30s..." | ||
| sleep 30 | ||
| done | ||
| - ./gradlew test | ||
| artifact_paths: | ||
| - "build/reports/tests/**/*" | ||
|
|
||
| # ─── Windows ────────────────────────────────────────────────────────────── | ||
| - label: ":windows: UI Tests (Windows)" | ||
| key: ui-test-windows | ||
| agents: | ||
| os: windows | ||
| queue: default | ||
| env: | ||
| JAVA_HOME: "C:\\Program Files\\Zulu\\zulu-21" | ||
| commands: | ||
| - start gradlew.bat runIdeForUiTests | ||
| - | | ||
| for /L %%i in (1,1,15) do ( | ||
| curl -sf http://127.0.0.1:8082 >nul 2>&1 && (echo IDE is ready & goto :ready) | ||
| echo Attempt %%i/15 - waiting 30s... | ||
| timeout /t 30 /nobreak >nul | ||
| ) | ||
| :ready | ||
| - gradlew.bat test | ||
| artifact_paths: | ||
| - "build/reports/tests/**/*" | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,56 @@ | ||||||||||||||||||||||||||||||
| # Main CI pipeline — replaces .github/workflows/build.yml | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| steps: | ||||||||||||||||||||||||||||||
| - label: ":test_tube: Test" | ||||||||||||||||||||||||||||||
| key: test | ||||||||||||||||||||||||||||||
| command: "gradle check" | ||||||||||||||||||||||||||||||
| plugins: | ||||||||||||||||||||||||||||||
| - docker#v5.12.0: | ||||||||||||||||||||||||||||||
| image: "gradle:9-jdk21" | ||||||||||||||||||||||||||||||
| artifact_paths: | ||||||||||||||||||||||||||||||
| - "build/reports/tests/**/*" | ||||||||||||||||||||||||||||||
| - "build/reports/kover/report.xml" | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| - label: ":hammer: Build" | ||||||||||||||||||||||||||||||
| key: build | ||||||||||||||||||||||||||||||
| # buildSearchableOptions launches a full IntelliJ IDE process and requires ~4GB RAM. | ||||||||||||||||||||||||||||||
| # Skip it in CI; it only generates marketplace search indexes. | ||||||||||||||||||||||||||||||
| command: "gradle buildPlugin -x buildSearchableOptions" | ||||||||||||||||||||||||||||||
| plugins: | ||||||||||||||||||||||||||||||
| - docker#v5.12.0: | ||||||||||||||||||||||||||||||
| image: "gradle:9-jdk21" | ||||||||||||||||||||||||||||||
| artifact_paths: | ||||||||||||||||||||||||||||||
| - "build/distributions/*.zip" | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| - label: ":shield: Verify Plugin" | ||||||||||||||||||||||||||||||
| key: verify | ||||||||||||||||||||||||||||||
| command: "gradle verifyPlugin -x buildSearchableOptions --no-daemon --max-workers 1" | ||||||||||||||||||||||||||||||
| plugins: | ||||||||||||||||||||||||||||||
| - docker#v5.12.0: | ||||||||||||||||||||||||||||||
| image: "gradle:9-jdk21" | ||||||||||||||||||||||||||||||
| artifact_paths: | ||||||||||||||||||||||||||||||
| - "build/reports/pluginVerifier/**/*" | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| - wait: ~ | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| - label: ":github: Create Release Draft" | ||||||||||||||||||||||||||||||
| key: release-draft | ||||||||||||||||||||||||||||||
| branches: "main" | ||||||||||||||||||||||||||||||
| agents: | ||||||||||||||||||||||||||||||
| queue: default | ||||||||||||||||||||||||||||||
| env: | ||||||||||||||||||||||||||||||
| GITHUB_TOKEN: $${GITHUB_TOKEN?} | ||||||||||||||||||||||||||||||
| commands: | ||||||||||||||||||||||||||||||
| - | | ||||||||||||||||||||||||||||||
| PROPERTIES="$$(gradle properties --console=plain -q)" | ||||||||||||||||||||||||||||||
| VERSION="$$(echo "$$PROPERTIES" | grep "^version:" | cut -f2- -d ' ')" | ||||||||||||||||||||||||||||||
| CHANGELOG="$$(gradle getChangelog --unreleased --no-header --console=plain -q)" | ||||||||||||||||||||||||||||||
|
Comment on lines
+41
to
+47
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: cat -n .buildkite/pipeline.ymlRepository: z8dn/advanced-android-project-view Length of output: 2177 🏁 Script executed: fd -type f gradle-wrapperRepository: z8dn/advanced-android-project-view Length of output: 248 🌐 Web query:
💡 Result: In Buildkite pipeline YAML, particularly within env blocks, use $$ to escape the $ character for runtime environment variable interpolation. This prevents premature substitution during pipeline upload and allows the variable to be evaluated when the agent runs the step. For example: - command: "deploy.sh $$SERVER" env: SERVER: "server-a" Here, $$SERVER becomes Citations:
🏁 Script executed: find . -name "gradlew*" -o -name "gradle-wrapper*" 2>/dev/nullRepository: z8dn/advanced-android-project-view Length of output: 180 🏁 Script executed: git ls-files | grep -i gradleRepository: z8dn/advanced-android-project-view Length of output: 363 Use This step runs on the default agent (line 40: The Proposed fix env:
GITHUB_TOKEN: $${GITHUB_TOKEN?}
commands:
- |
- PROPERTIES="$$(gradle properties --console=plain -q)"
+ PROPERTIES="$$(./gradlew properties --console=plain -q)"
VERSION="$$(echo "$$PROPERTIES" | grep "^version:" | cut -f2- -d ' ')"
- CHANGELOG="$$(gradle getChangelog --unreleased --no-header --console=plain -q)"
+ CHANGELOG="$$(./gradlew getChangelog --unreleased --no-header --console=plain -q)"📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| gh api repos/{owner}/{repo}/releases \ | ||||||||||||||||||||||||||||||
| --jq '.[] | select(.draft == true) | .id' \ | ||||||||||||||||||||||||||||||
| | xargs -I '{}' gh api -X DELETE repos/{owner}/{repo}/releases/{} | ||||||||||||||||||||||||||||||
|
Comment on lines
+49
to
+51
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Lists the draft releases the current command would delete.
gh api repos/{owner}/{repo}/releases \
--jq '.[] | select(.draft == true) | [.id, .tag_name, .name] | `@tsv`'Repository: z8dn/advanced-android-project-view Length of output: 101 🏁 Script executed: cat -n .buildkite/pipeline.yml | sed -n '40,60p'Repository: z8dn/advanced-android-project-view Length of output: 822 Limit draft deletion to the release tag being regenerated. This deletes every draft release in the repository (line 49-51) before creating Safer tag-scoped deletion- gh api repos/{owner}/{repo}/releases \
- --jq '.[] | select(.draft == true) | .id' \
- | xargs -I '{}' gh api -X DELETE repos/{owner}/{repo}/releases/{}
+ DRAFT_ID="$$(gh api repos/{owner}/{repo}/releases \
+ --jq ".[] | select(.draft == true and .tag_name == \"v$$VERSION\") | .id")"
+ if [ -n "$$DRAFT_ID" ]; then
+ gh api -X DELETE "repos/{owner}/{repo}/releases/$$DRAFT_ID"
+ fi📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| gh release create "v$$VERSION" \ | ||||||||||||||||||||||||||||||
| --draft \ | ||||||||||||||||||||||||||||||
| --title "v$$VERSION" \ | ||||||||||||||||||||||||||||||
| --notes "$$CHANGELOG" | ||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🌐 Web query:
Buildkite pipeline YAML env variable interpolation escaped dollar signs in env blocks💡 Result:
In Buildkite pipeline YAML files, particularly when using buildkite-agent pipeline upload, environment variable interpolation occurs using$VAR or $ {VAR} syntax. To prevent interpolation during upload (so the variable is evaluated at runtime instead), escape the dollar sign with $$ or $ in commands, env blocks, or other strings. Example from official docs: - command: "deploy.sh $$SERVER" env: SERVER: "server-a" Here, $$SERVER becomes $SERVER at runtime, allowing the job's environment to interpolate it. This applies to env blocks in steps. Top-level env in pipeline.yml sets variables for subsequent steps and is available for interpolation in further uploads. For literal dollars without interpolation, use $ $ or $. Use --no-interpolation flag on pipeline upload to disable entirely (agent v3.1.1+). Sources confirm this behavior consistently across docs and GitHub issues.
Citations:
Do not escape
QODANA_TOKENwith double dollar signs in theenv:block.Using
$${QODANA_TOKEN?}prevents Buildkite's upload-time interpolation, leaving${QODANA_TOKEN?}as a literal environment variable value that Docker receives instead of the actual token. Either use unescaped${QODANA_TOKEN?}to interpolate the secret during pipeline upload, or configure the token to be injected at runtime through Buildkite's agent secrets mechanism.🤖 Prompt for AI Agents